本帖最后由 Simpon 于 2016-10-19 11:35 编辑
运行循环官方示意图
file:///Users/LilMing/Desktop/8%E6%9C%88%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/%E5%B9%BF%E5%B7%9E%E6%A0%A1%E5%8C%BA/images/001-%E8%BF%90%E8%A1%8C%E5%BE%AA%E7%8E%AF%E7%A4%BA%E6%84%8F%E5%9B%BE.png 基本概念运行循环(消息循环)的目的- 保持程序的 持续运行, 保证 程序不退出
- 处理 App 运行中的各种事件(比如触摸事件、定时器事件、performSelector)
- 如果没有 事件发生,程序自动进入 休眠 状态,该做事时做事,该休息时休息,达到 省电 的 目的
运行循环保证程序不退出- 新建一个 iOS 工程
运行,发现程序启动,不做任何操作,程序 没有退出 [Objective-C] 纯文本查看 复制代码 #import <UIKit/UIKit.h>[/align] #import "AppDelegate.h"
int main(int argc, char * argv[])
{
@autoreleasepool {
NSLog(@"程序运行了.....");
int result = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"程序即将退出了...., result = %d", result);
return result;
}
} - RunLoop 保持了程序的 持续运行
- 因为在 UIApplicationMain 自动开启了一个 RunLoop,所以 UIApplicationMain 函数一直没有 返回,保持了程序的 持续运行
这个默认启动的 RunLoop 是跟 主线程 相关联的 没有 运行循环,程序执行完 main 函数就退出了
去掉 UIApplicationMain 代码 [Objective-C] 纯文本查看 复制代码 #import <UIKit/UIKit.h>[/align]#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"程序运行了.....");
int result = -1;
NSLog(@"程序即将退出了...., result = %d", result);
return result;
}
} - 控制台输出 objc 01.运行循环概念[1538:74836] 程序运行了..... 01.运行循环概念[1538:74836] 程序即将退出了...., result = -1
- 程序立马退出了
RunLoop 实现伪代码 [Objective-C] 纯文本查看 复制代码 int main(int argc, char * argv[]) {
NSLog(@"程序运行了.....");
BOOL running = YES;
do {
// 执行任务,处理事件
// 没事件就休眠
} while (running);
NSLog(@"程序即将退出了...., result = %d", result);
return 0;
}
RunLoop对象- iOS中有2套API来访问和使用 RunLoop
- Foundation
- Core Foundation
- NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 对象
- NSRunLoop 是基于 CFRunLoopRef 的 OC 封装
- RunLoop 官方文档: 搜索 run loop 选择 Getting a Run Loop Object
RunLoop 与线程关系- 每条线程都有 唯一 的 一个 与之对应的 RunLoop 对象
- 主线程的 RunLoop 在应用 启动 的时候就会 自动创建
- 子线程的 RunLoop 需要 手动创建
- RunLoop 在第一次获取时 创建 (懒加载),在 线程结束 时 销毁
获取 RunLoop 对象Foundation
- 获得当前线程的 RunLoop 对象: [NSRunLoop currentRunLoop];
- 获得主线程的 RunLoop 对象: [NSRunLoop mainRunLoop];
[AppleScript] 纯文本查看 复制代码
(BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions {
// 获取当前线程的RunLoop对象
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
NSLog(@"currentRunLoop = %@", currentRunLoop);
// 获取主线程的RunLoop对象
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
NSLog(@"mainRunLoop = %@", mainRunLoop);
// 创建一个新的线程
[NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
return YES;
}
(void)demo {// 获取当前线程的RunLoop对象, currentRunLoop是懒加载的,只有第一次访问的时候才去创建
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
NSLog(@"子线程currentRunLoop = %p", currentRunLoop);
}
运行结果: 01.运行循环概念[1818:115628] currentRunLoop = 0x7fd8d2e04810 01.运行循环概念[1818:115628] mainRunLoop = 0x7fd8d2e04810 01.运行循环概念[1818:115684] 子线程 currentRunLoop = 0x7fd8d2e65300 Core Foundation
- 获得当前线程的RunLoop对象: CFRunLoopGetCurrent();
- 获得主线程的RunLoop对象: CFRunLoopGetMain();
[Objective-C] 纯文本查看 复制代码 (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions { // 获取当前线程的RunLoop对象
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
NSLog(@"currentRunLoop = %@", currentRunLoop);
// 获得主线程的RunLoop对象
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
NSLog(@"mainRunLoop = %@", mainRunLoop);
// 创建一个新的线程
[NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
return YES; } [Objective-C] 纯文本查看 复制代码 (void)demo {
// 获取当前线程的RunLoop对象
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
NSLog(@"子线程 currentRunLoop = %p", currentRunLoop);
}
运行结果:01.运行循环概念[1833:116567] currentRunLoop = 0x7f8f30e054f0 01.运行循环概念[1833:116567] mainRunLoop = 0x7f8f30e054f0 01.运行循环概念[1833:116643] 子线程 currentRunLoop = 0x7f8f30f09950 运行循环模式
运行循环 的输入事件file:///Users/LilMing/Desktop/8%E6%9C%88%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/%E5%B9%BF%E5%B7%9E%E6%A0%A1%E5%8C%BA/images/001-%E8%BF%90%E8%A1%8C%E5%BE%AA%E7%8E%AF%E7%A4%BA%E6%84%8F%E5%9B%BE.png * Runloop 接收输入事件来自两种不同的来源:输入源(input source)和 定时源(timer source) * 输入源 事件包括用户在屏幕上的 触摸 事件,NSPort 对象和 performSelector:onThread: * 定时源 指的是定时器 NSTimer NSTimer- 通过定时器,了解消息循环的模式
- 定时器执行的方法中不易执行太耗时的操作,否则就会降低用户体验,用户拖拽的时候会感觉到卡顿的现象
- 定时器在不使用时,一定注意销毁,否则会出现内存泄漏
- 理解运行循环的两种模式
- NSDefaultRunLoopMode 默认模式
- NSRunLoopCommonModes
- NSDefaultRunLoopMode
- UITrackingRunLoopMode
- 使用消息循环的时候必须指定两件事情
- 消息循环运行在某一种 消息循环模式 上,处理该模式里面的 输入源 或 定时源
NSTimer 练习- 在 ViewController 里面创建 NSTimer 对象 ```objc
[Objective-C] 纯文本查看 复制代码 (void)viewDidLoad {
[super viewDidLoad];
// 创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(display) userInfo:nil repeats:YES];
// 将定时器添加到 RunLoop 的 NSDefaultRunLoopMode 模式里面 主线程RunLoop默认的模式
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
/// NSTimer过1.0秒调用一次
(void)display {
NSLog(@"定时器方法被调用了: runloop = %@", [[NSRunLoop currentRunLoop] currentMode]);
}
- 运行结果:正常状态输出,主线程RunLoop默认的模式。当滚动 UITextView 时不会输出 objc 01.运行循环概念[3663:491230] 定时器方法被调用了: runloop = kCFRunLoopDefaultMode 01.运行循环概念[3663:491230] 定时器方法被调用了: runloop = kCFRunLoopDefaultMode 01.运行循环概念[3663:491230] 定时器方法被调用了: runloop = kCFRunLoopDefaultMode
- 当 UIScrollView 滚动时,进入 UITrackingRunLoopMode 模式
- 将 NSTimer 添加到 NSRunLoopCommonModes 模式里面
[AppleScript] 纯文本查看 复制代码 (void)viewDidLoad {
[super viewDidLoad];
// 创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(display) userInfo:nil repeats:YES];
// 将定时器添加到 RunLoop 的 NSDefaultRunLoopMode 模式里面 主线程RunLoop默认的模式 //
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 将定时器添加到 RunLoop 的 NSRunLoopCommonModes 模式里面,当UIScrollView滚动时,进入 UITrackingRunLoopMode 模式
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- NSRunLoopCommonModes 包含 NSDefaultRunLoopMode 和 UITrackingRunLoopMode 2中模式,所以在正常状态 和 UITextView 滚动时都能调用 NSTimer
- 将 NSTimer 添加到 UITrackingRunLoopMode 模式里面
[Objective-C] 纯文本查看 复制代码 (void)viewDidLoad {
[super viewDidLoad];
// 创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(display) userInfo:nil repeats:YES];
// 将定时器添加到 RunLoop 的 NSDefaultRunLoopMode 模式里面 主线程RunLoop默认的模式
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 将定时器添加到 RunLoop 的 NSRunLoopCommonModes 模式里面,当UIScrollView滚动时,进入 UITrackingRunLoopMode 模式
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 当UIScrollView滚动时,进入 UITrackingRunLoopMode 模式, 将 timer 加入到 UITrackingRunLoopMode,只在 UITrackingRunLoopMode下才会执行timer, 在 NSDefaultRunLoopMode 模式里面不会执行
[[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
}
- RunLoop 默认是在 NSDefaultRunLoopMode,所以不会输出,只有当 UITextView 滚动时进入 UITrackingRunLoopMode, NSTimer 才执行
子线程的消息循环- 新建一个 iOS 工程
- 在 Main.storyboard 中拖入一个 UITextView 控件
- 在 ViewController 控制器的 viewDidLoad 创建一个定时器
[AppleScript] 纯文本查看 复制代码 -(void)viewDidLoad {
[super viewDidLoad]; // 创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES]; // 将定时器添加到主线程RunLoop里面,定时器的方法也是在主线程上面执行,会影响界面流畅
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
} - 实现定时器方法 time,里面模拟耗时操作
[Objective-C] 纯文本查看 复制代码 /// 耗时的定时任务
(void)time {
NSLog(@"time, thread = %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
} - 运行可以看到 UI 界面滚动卡顿,因为定时器是添加在 主线程 的 RunLoop 上面, time 定时方法是在主线程上面执行 bash 02-子线程消息循环[2635:240723] time, thread = <NSThread: 0x7fe381f06c20>{number = 1, name = main} 02-子线程消息循环[2635:240723] time, thread = <NSThread: 0x7fe381f06c20>{number = 1, name = main} 02-子线程消息循环[2635:240723] time, thread = <NSThread: 0x7fe381f06c20>{number = 1, name = main}
- 在子线程上面执行定时器任务
[Objective-C] 纯文本查看 复制代码
- (void)viewDidLoad {
[super viewDidLoad]; // 创建定时器 //
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES]; //
// 将定时器添加到主线程RunLoop里面,定时器的方法也是在主线程上面执行,会影响界面流畅 //
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 在子线程上开启RunLoop,将定时器添加到子线程的RunLoop上面
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[_thread start];
} - 在 viewDidLoad 中创建一个子线程 objc @interface ViewController () { NSThread *_thread; } @end
- 定义子线程执行的方法 run
[Objective-C] 纯文本查看 复制代码 /// 子线程执行方法
-(void)run {
NSLog(@"子线程开始, thread = %@", [NSThread currentThread]);
NSLog(@"子线程结束");
}
- 运行,可以看到子线程执行了 run 方法里面的代码就退出了
- 在 run 方法中创建定时器
[Objective-C] 纯文本查看 复制代码 /// 子线程执行方法
(void)run {
NSLog(@"子线程开始, thread = %@", [NSThread currentThread]);
// 创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES];
// 将 timer 定时器添加到当前线程 RunLoop 的 NSDefaultRunLoopMode 模式里面
// [NSRunLoop currentRunLoop] 当前线程/子线程 的RunLoop
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
NSLog(@"子线程结束");
}
- 运行,发现定时器并没有执行,run 方法依然直接结束了.
- 开启当前线程的 RunLoop: [[NSRunLoop currentRunLoop] run];
[AppleScript] 纯文本查看 复制代码 /// 子线程执行方法
(void)run {
NSLog(@"子线程开始, thread = %@", [NSThread currentThread]);
// 创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES];
// 将 timer 定时器添加到当前线程 RunLoop 的 NSDefaultRunLoopMode 模式里面
// [NSRunLoop currentRunLoop] 当前线程/子线程 的RunLoop
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 启动RunLoop
[[NSRunLoop currentRunLoop] run];
NSLog(@"子线程结束");
}
- 运行: objc 01:46:20.195 02-子线程消息循环[2673:248643] 子线程开始, thread = <NSThread: 0x7f822ad90530>{number = 2, name = (null)} 01:46:21.198 02-子线程消息循环[2673:248643] time, thread = <NSThread: 0x7f822ad90530>{number = 2, name = (null)} 01:46:24.198 02-子线程消息循环[2673:248643] time, thread = <NSThread: 0x7f822ad90530>{number = 2, name = (null)} 01:46:27.200 02-子线程消息循环[2673:248643] time, thread = <NSThread: 0x7f822ad90530>{number = 2, name = (null)}
- 可以看到,RunLoop 启动了,在不停的处理 定时源,此时子线程就不会结束,直到 RunLoop 退出
- 注释 将定时器添加到 RunLoop 的代码,如果只是单纯启动 RunLoop,RunLoop 没有定时源或输入源会立马就退出了
- 所以在启动 RunLoop 时需要保证 RunLoop 有定时源或输入源
- 在子线程执行特定的任务,取消 time 方法中的 线程睡眠
[Objective-C] 纯文本查看 复制代码 /// 耗时的定时任务
(void)time {
NSLog(@"time, thread = %@", [NSThread currentThread]);
// [NSThread sleepForTimeInterval:2];
}
// 在 Main.storyboard 拖入一个按钮,连线到 ViewController 的 buttonClick: 方法
/// 按钮点击事件
(IBAction)buttonClick:(id)sender {
} - 在 buttonClick: 中让 子线程 执行特定的方法
[Objective-C] 纯文本查看 复制代码 /// 按钮点击事件
- (IBAction)buttonClick:(id)sender {
[self performSelector:@selector(perform) onThread:_thread withObject:nil waitUntilDone:YES];
}
- (void)perform {
NSLog(@"perform, thread = %@", [NSThread currentThread]);
}
- 运行:可以看到,子线程 RunLoop 在执行定时器的同时,还能接受 performSelector:onThread:withObject:waitUntilDone 发来的 输入源 事件 objc 01:57:12.560 02-子线程消息循环[2767:257502] 子线程开始, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:13.582 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:14.581 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:15.506 02-子线程消息循环[2767:257502] perform, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:15.583 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:16.581 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:17.130 02-子线程消息循环[2767:257502] perform, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:17.582 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)}
|