什么是Run Loops
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出。这时候可以使用一个循环来进行实现,但是如果单单使用循环的会一直占用资源。因此我们就需要让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。这就是RunLoop所做的事。
线程和 RunLoop 之间是一一对应的。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
Mode
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。
有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。
CFRunLoopSourceRef 是事件产生的地方
• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
循环步骤
AutoreleasePool
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
PerformSelecter
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
SDWebImage
在SDWebImageDownloaderOperation中,
- (void)start{
...
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
...
if(self.connection){
...
CFRunLoopRun()
...
}
}
- (void)cancelInternalAndStop {
if (self.isFinished) return;
[self cancelInternal];
CFRunLoopStop(CFRunLoopGetCurrent());
}
在创建self.connection成功后,执行了CFRunLoopRun(),开启了runloop。在failed或finished的时候会调用CFRunLoopStop停止runloop。如果不开启runloop的话,在执行完start()后任务就完成了,NSURLConnection的代理就不会执行了。runloop相当于子线程的循环,可以灵活控制子线程的生命周期。
1.https://blog.ibireme.com/2015/05/18/runloop/
2.https://github.com/ming1016/study/wiki/CFRunLoop