前言:
第一次知道Runloop,是在处理定时器的时候碰到的,当时觉得很难(实际上也挺难的),从字面的意思上看又跑圈 / 运行循环的意思, 其实也差不多;
Runloop的作用:
保持程序的持续运行; 处理APP中的各种事件;节省CPU的资源;
为什么会出现Runloop?
如果没有Runloop,程序只能从上到下执行, 只会运行一遍,从这歌问题考虑,可以稍微的理解Runloop的原理实际上就是一个while大循环;
默认启动的Runloop都是跟主线程相关联的;
什么是Runloop?
1.从字面意思看,就是一个运行循环;
2.在它内部 是一个do- while循环,不断的处理各种任务 ;
3.一个线程对应一个runloop,主线程的runllop默认已经启动,子线程的runllop需要自己手动启动;
4.runloop只能选择一个模式启动,如果当前model中没有任何 source timer observe,那么就直接退出runloop;
实现Runloop的方式?
iOS中有有两套api可以获取 访问 和使用Runloop;
Foundation提供的NSRunloop;
Core Foundation 提供的CFRunLoopRef;
NSRunloop 跟 CFRunLoopRef 都代表 runloop对象;
NSRunloop是基于CFRunloop的,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API;
Runloop跟线程的关系?
每条线程都有唯一一个与之对应的Runloop对象;
主线程的Runloop默认创建,子线程手动创建;
Runloop在第一时间创建, 在线程消亡的时候 死亡, Runloop是寄生在线程上的;
Runloop的相关类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimeRef
CFRunLoopObserverRef
runloop饰事件驱动,如果runllop中没有 source time observer ,runloop 就会一直睡下去;
什么时候使用runloop?
1.开启一个常驻线程(让一个子线程不进去消亡状态,等待其他线程发来消息,处理其他事件);
2.在子线程中开一个定时器;
3.在子线程中进行一些长期监控;
4.可以控制定时器在那那种模式下执行;
5.可以让某些事件(行为/任务)在特定的模式下执行;
6.可以添加observer监听runloop事件
事件循环:
每个线程都有一个runloop对象,只有主线程的runloop默认时开启的,在子线程中我们调用[NSRunLoop currentRunLoop]; 获取RunLoop对象的时候,就会创建RunLoop; 一个线程可以开辟多个runloop,都是嵌套在最大的runloop中;
runloop的一些优点:
保证程序持续运行;
处理app中的各种事件
节省cpu时间,在程序启动后,如果什么都没有执行的话,就进入睡眠状态;
runloop的构成元素:
1.CFRunLoopModeRef: 代表Runloop的运行模式;
一个Runloop包含n多个model,每个model中又包含了n多个source/timer/obsercver;
一个Runloop在同一时间内只能处于一种模式下工作,这个模式也就是currentMode;
一个Runloop的mode 是可以自由切换的;
系统默认注册了5种运行mode:
NSDefaultRunLoopMode: 默认的Mode也是空闲线程,通常主线程的RunLoop是在这个Mode下运行
UITrackingRunLoopMode: 界面跟踪Mode,当用户与界面交互的时候会在此Mode下运行
NSRunLoopCommonModes: 这个不是一种真正的Mode,是一个占位用的Mode
UIInitializationRunLoopMode: 程序启动时(初始化)的Mode,启动完成后就不在此Mode下
GSEventReceiveRunLoopMode: 接受系统事件的内部Mode,一般我们用不到
NSRunLoopCommonModes的应用实例:
当我们在做图片轮播器的时候,如果使用的是kCFRunLoopDefaultMode那么当ScrollView滚动的时候,RunLoop模式就会切换为UITrackingRunLoopMode,这时候NSTimer就没法执行,这时候我们可以使用kCFRunLoopCommonModes,就可以解决这个问题。
2. CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器(时间到了就触发)
CFRunLoopTimerRef基本上说的就是NSTimer,它受RunLoop Mode的影响;
另外 CADisplayLink也可以在Runloop上跑
3. CFRunLoopSourceRef (事件源/输入源)
按照官方文档CFRunLoopSourceRef为3类:
1. Port-Based Sources:与内核相关,基于端口,跟其它线程进行交互的;
2. Custom Input Sources:与自定义Sources相关;
3. Cocoa Perform Selector Sources:与Performxxxxxx等方法等相关
按照函数调用栈CFRunLoopSourceRef分2类:
1. Source0:非基于Port的;
2. Source1:基于Port的,通过内核和其他线程通信,接收、分发系统事件;
4. CFRunLoopObserverRef
CFRunLoopObserverRef是RunLoop的观察者,可以通过CFRunLoopObserverRef来监听RunLoop状态的改变,CFRunLoopObserverRef监听的状态由以下几种:
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
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.将观察者CFRunLoopObserverRef添加到RunLoop上面
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
3.观察者CFRunLoopObserverRef要手动释放 (并不遵循ARC,需要手动释放,不然会有内存问题)
CFRelease(observer);
runloop的处理逻辑
上图显示了线程的输入源
1.基于端口的输入源(Port Sources)
2.自定义输入源(Custom Sources)
3.Cocoa执行Selector的源(performSelectorxxxx方法)
4.定时源(Timer Sources )
线程针对上面不同的输入源,有不同的处理机制
1.handlePort——处理基于端口的输入源
2.customSrc——处理用户自定义输入源
3.mySelector——处理Selector的源
4.timerFired——处理定时源
runloop的具体使用:
1. 可以让图片延时显示:(重绘试图)
比如要 渲染很多图片,但是又不希望出现卡顿的情况, 可以考虑使用 NSDefaultRunLoopMode 来实现 ;
当相关的视图对象接收到设置属性的消息的时候,就会将自己标记为要重绘。RunLoop会收集所有等待重绘制的视图,苹果会注册一个CFRunLoopObserver来监听kCFRunLoopBeforeWaiting事件,当事件触发的时候,就会对所有等待重绘的视图对象发送drawRect:消息。
[self.headerIV performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"1"] afterDelay:3.0f inModes:@[NSDefaultRunLoopMode]];
2.常驻线程 (保证一个线程永远不死)
self.thread = [[ZKThread alloc]initWithTarget:self selector:@selector(execute) object:nil];
[self.thread start];
[self performSelector:@selector(runAction) onThread:self.thread withObject:nil waitUntilDone:NO];
- (void)run{
NSLog(@"run----%@",[NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"----------");
}
3.一个TableView延迟加载图片的新思路
当cell上有需要从网络获取的图片的时候,我们滚动tableView,异步线程会去加载图片,加载完成后主线程就会设置cell的图片,这个时候就会出现卡的现象。
一般的解决方案是调用tableView的代理方法,判断tableView是否正在滑动,如果在滑动,就不设置图片,等停止滑动后再去设置cell的图片。用Runloop能更简单的解决这个问题。我们可以根据RunLoop不同Mode下,执行不同的事件来解决这个问题思路如下:
当设置图片的时候,让其在 CFRunLoopDefaultMode 下进行。当滚动tableView的时候,RunLoop是在
UITrackingRunLoopMode 这个Mode下,就不会设置图片,当停止的时候,就会设置图片。
UIImage*downloadedImage = ...;[self.avatarImageViewperformSelector:@selector(setImage:)withObject:downloadedImageafterDelay:0inModes:@[NSDefaultRunLoopMode]];
4.让Crash的App回光返照
App崩溃的发生分两种情况:
(1)program received signal:SIGABRT SIGABRT一般是过度release 或者 发送 unrecogized selector导致。
由 SIGABRT 引起的Crash 是系统发这个signal给App,程序收到这个signal后,就会把主线程的RunLoop杀死,程序就Crash了 该例只针对 SIGABRT引起的Crash有效。
Signal:
是Unix、类Unix等操作系统中进程间通讯的一种方式,用来通知一个事件发生。当一个singal发送给进程,操作系统就会中断进程的正常控制流程,
如果在进程中定义了信号的处理函数,那么这个函数就会被执行,因此我们可以注册signal,并指定收到signal后要执行的函数
为了让App回光返照,我们需要来捕获libsystem_sim_c.dylib调用 abort() 函数发出的程序终止信号,然后让其执行我们定义的处理signal的方法。在方法中,我们需要开启一个RunLoop,保持主线程不退出。
(2)EXC_BAD_ACCESS是访问已被释放的内存导致,野指针错误。
参考文档:http://www.jianshu.com/p/4bc01f5269e7 http://www.jianshu.com/p/549c37f60bf7