</pre>一直觉得ios中的runloop特别重要,想要理解一下,然后在开发过程中希望能够用runloop在某些地方写些跟优雅的代码。<p></p><p>在网上看了些资料,自己总结如下吧!</p><p>很早以前,我研究过windows编程,其中就认识到原来应用可以一直处于运行状态是因为里边有一个while死循环,让应用根本没办法从main入口函数中退出!真是人才,也很容易理解这一点吧!而这样的情况放在ios中就是我们接下来要讲的runloop。</p><p>runloop的作用:</p><p>1.保持程序不会退出</p><p style="text-align:justify">2.程序不退出,就可以随时处理app中的各种事件,包括人为的事件,定时器事件,selector事件等等</p><p style="text-align:justify">3.节省cup资源,提高程序的性能,有事情做的时候就唤醒,没事情做的时候就睡眠状态</p><p style="text-align:justify"></p><pre name="code" class="objc">int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
其中UIApplicationMain这个函数就有一个Runloop,这个Runloop就是主线程中的Runloop,所以主线程才一直不会退出,所以应用也不会结束.
Foundation框架中的NSRunLoop与Core Foundation中的CFRunLoop
RunLoop与线程有直接的非常不同寻常的关系,形影不离
1.没一条线程都有一个唯一的与之对应的RunLoop对象
2.主线程的RunLoop是由系统自己创建的,然后子线程的RunLoop对象我们如果要使用就必须自己去创建
3.一个线程结束了,那么这个线程所对应的RunLoop对象就会销毁
下面来分析下RunLoop的结构
一个RunLoop中可以拥有多个不同的Mode,但是RunLoop在同一时间运行只能去使用其中某一个Mode,如果要切换Mode,必须先退出之前的那个Mode,才能进入新的Mode中。
这里所说的Mode,其实就是指RunLoop的不同的运行模式,它有五种运行模式,如下
1.NSDefaultRunLoopMode 主线程的RunLoop就是在这种模式下运行
2.UITrackingRunLoopMode 界面跟踪模式,当用户与界面交互的时候会在这种模式下运行RunLoop
3.NSRunLoopCommonModes 模式占位,上边的两种模式都可以运行RunLoop
4.UIInitializationRunLoopMode 程序启动时处于这个模式下,程序一点启动完毕就不会在这个模式下了
5.GSEventReceiveRunLoopMode 接受系统事件的内部模式
每个CFRunLoopModeRef中又包含多个Source,Timer,Observer,包含的这三个东东,其实就是去触发RunLoop唤醒不死线程的作用(Observer好像不是这个作用,我暂且就这么理解吧!),下边我们来一一讲解:
CFRunLoopTimerRef:
屁话不多说,其实就是个NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];<span style="font-family: Arial, Helvetica, sans-serif;"> </span>
<span style="font-family: Arial, Helvetica, sans-serif;">//将创建的timer加入到DefaultRunLoopMode中,而这个timer在其他模式,比如TrackingRunLoopMode下会停止工作</span>
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
</pre><pre name="code" class="objc">
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 拖拽UI界面时出发定时器,在默认模式(NSDefaultRunLoopMode)下不工作
[[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// NSRunLoopCommonModes仅仅是标记NSTimer在两种模式(UITrackingRunLoopMode/NSDefaultRunLoopMode)下都能运行,但一个RunLoop中同一时间内只能运行一种模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 默认已经添加到主线程中RunLoop(mainRunLoop)中(Mode:NSDefaultRunLoopMode)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
</pre>看过别人写的简书的以为作者<a target=_blank target="_blank" class="author-name blue-link" href="http://www.jianshu.com/users/244aa1f48d1c" style="color:rgb(64,148,199); text-decoration:none; margin:0px 5px; font-family:'lucida grande','lucida sans unicode',lucida,helvetica,'Hiragino Sans GB','Microsoft YaHei','WenQuanYi Micro Hei',sans-serif; line-height:20px">YotrolZ </a>写过的一篇文章中讲到:<p></p><p style="text-align:justify">GCD中的定时器跟NSTimer计时器是不一样的,GCD定时器不受Mode的yin'xiang</p><p style="text-align:justify"></p><p style="text-align: justify;"></p><pre name="code" class="objc">/** 定时器对象 */
@property (nonatomic, strong)dispatch_source_t timer;
// 需要一个强引用
NSLog(@"开始");
// 获取队并发队列,定时器的回调函数将会在子线程中执行
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 获取主队列,定时器的回调函数将会在子线程中执行
dispatch_queue_t queue = dispatch_get_main_queue();
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 该时间表示从现在开始推迟两秒
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
// 设置定时器的开始时间,间隔时间
dispatch_source_set_timer(self.timer, start, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------%@", [NSThread currentThread]);
});
dispatch_resume(self.timer);
/* 参数说明:
// 设置定时器的一些属性
dispatch_source_set_timer(dispatch_source_t source, // 定时器对象
dispatch_time_t start, // 定时器开始执行的时间
uint64_t interval, // 定时器的间隔时间
uint64_t leeway // 定时器的精度
);
*/
1.Port-based Sources : 内核相关
2.Custom Input Sources : 与自定义Sources相关
3.Cocoa Perform Selector Sources : 与self performSelector方法相关
4.Source0 : 非基于port
5. Source1 : 基于port
CFRunLoopObserverRef:
CFRunLoopObserverRef是RunLoop的观察者,可以通过CFRunLoopObserverRef来监听RunLoop的状态改变.
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 状态值:1,表示进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 状态值:2,表示即将处理NSTimer
kCFRunLoopBeforeSources = (1UL << 2), // 状态值:4,表示即将处理Sources
kCFRunLoopBeforeWaiting = (1UL << 5), // 状态值:32,表示即将休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 状态值:64,表示从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 状态值:128,表示退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 表示监听上面所有的状态
};
那么如何监听RunLoop的状态啦?
1.先创建CFRunLoopObserverRef
// 第一个参数用于分配该observer对象的内存
// 第二个参数用以设置该observer所要关注的的事件,详见回调函数myRunLoopObserver中注释
// 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
// 第四个参数用于设置该observer的优先级,一般为0
// 第五个参数用于设置该observer的回调函数
// 第六个参数observer的运行状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});
2.将观察者添加到对应的RunLoop上
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
3.C语言中的对象要主动释放
CFRelease(observer);
RunLoop的具体使用
-
1.图片刷新(假如界面要刷新N多图片(渲染),此时用户拖拽UI控件就会出现卡的效果,我们可以通过RunLoop实现,只在RunLoop默认Mode下下载,也就是拖拽Mode下不刷新图片)
- (void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下执行(刷新图片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"0"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
}
2.保证一个线程永远不死
- 方案一:用一个强引用引用住线程(这种方案是不可行的),原因如下:
#import "ViewController.h" #import "YCThread.h" @interface ViewController () /* 思路:用一个强引用线程,当点击屏幕的时候再让他启动,结果是不可行!!!! 因为,线程执行完内部的任务后就会自动死亡,你如果用一个强引用引用这个线程, 即使内存还在,但是该线程也已经处于死亡状态(线程状态),是不能再次启动的, 如果再次启动一个死亡状态的线程,就会 报错--reason: '*** -[YCThread start]: attempt(视图) to start the thread again' */ /** 线程对象 */ @property (nonatomic, strong)YCThread *thread; // 强引用 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建子线程 self.thread = [[YCThread alloc] initWithTarget:self selector:@selector(run) object:nil]; // 启动子线程 [self.thread start]; } - (void)run { NSLog(@"----------"); } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 点击屏幕再次启动线程 [self.thread start]; } @end
- 方案二:(死循环+RunLoop),不建议此做法,不是太好
方案三:(子线程中加入RunLoop+RunLoop源)建议采用此方案#import "ViewController.h" @interface ViewController () /** 线程对象 */ @property (nonatomic, strong)NSThread *thread; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建子线程 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [self.thread start]; } - (void)run { NSLog(@"run--%@", [NSThread currentThread]); // 利用死循环(不建议此做法) while (1) { [[NSRunLoop currentRunLoop] run]; } } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]]; } - (void)test { NSLog(@"test--%@", [NSThread currentThread]); } @end
#import "ViewController.h" /* 思路:为了保证线程不死,我们考虑在子线程中加入RunLoop, 但是由于RunLoop中没有没有源,就会自动退出RunLoop, 所以我们要为子线程添加一个RunLoop, 并且为这个RunLoop添加源(保证RunLoop不退出) */ @interface ViewController () /** 线程对象 */ @property (nonatomic, strong)NSThread *thread; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建子线程 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; //启动子线程 [self.thread start]; } - (void)run { NSLog(@"run--%@", [NSThread currentThread]); // 子线程 // 给子线程添加一个RunLoop,并且加入源 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; // 启动RunLoop [[NSRunLoop currentRunLoop] run]; NSLog(@"------------"); // RunLoop启动,这句没有执行的机会 } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 在子线程中调用test方法,如果子线程还在就能够调用成功 [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]]; } - (void)test { NSLog(@"test--%@", [NSThread currentThread]); // 子线程 } @end