1.RunLoop与dispatch的关系
1.在__CFRunLoopRun函数中,用dispatch_source_create创建一个定时器。处理此次runLoop mode的运行时间,唤醒runloop。
2.使用dispatch执行的block(main_queue中),例如
dispatch_async 提交的任务
dispatch_after 提交的延时任务
dispatch_source_create
dispatch_source_set_timer
dispatch_source_set_event_handler
设置的定时器
它们的执行流程是
BeforeSources后,开始处理提交到Runloop的Block(通过-[NSRunLoop performBlock:]方式),然后开始处理source0,如果在source0中处理了,再处理一下Block。调用__CFRunLoopServiceMachPort,在其中用循环的方式通过mach_msg接受消息,mach_msg_header_t类型的消息体的msgh_local_port来自_dispatch_get_main_queue_port_4CF函数,当有dispatch的任务时,Runloop就会接收到消息。在调用CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE,其内部又调用了_dispatch_main_queue_callback_4CF,最终回到dispatch库中执行任务。
堆栈调用
* frame #0: 0x0000000103912c83 testblock`__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x000060000193ee20) at ViewController.m:82:10
frame #1: 0x00000001057ef9c8 libdispatch.dylib`_dispatch_client_callout + 8
frame #2: 0x00000001057f2316 libdispatch.dylib`_dispatch_continuation_pop + 557
frame #3: 0x0000000105805e8b libdispatch.dylib`_dispatch_source_invoke + 2205
frame #4: 0x00000001057fdca4 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 687
frame #5: 0x0000000104201dab CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
frame #6: 0x00000001041fc62e CoreFoundation`__CFRunLoopRun + 2685
frame #7: 0x00000001041fb6c6 CoreFoundation`CFRunLoopRunSpecific + 567
frame #8: 0x000000011010cdb3 GraphicsServices`GSEventRunModal + 139
frame #9: 0x00000001079ad187 UIKitCore`-[UIApplication _run] + 912
frame #10: 0x00000001079b2038 UIKitCore`UIApplicationMain + 101
frame #11: 0x000000010391431a testblock`main(argc=1, argv=0x00007ffeec2eed18) at main.m:18:12
frame #12: 0x000000010587e409 libdyld.dylib`start + 1
frame #13: 0x000000010587e409 libdyld.dylib`start + 1
2.Runloop与定时器的关系
dispatch类型的定时器,上面已经谈过了,我们谈下其他类型的定时器,
1.NSTimer类型的
这种类型会转换为CFRunLoopTimerRef,然后加入到RunLoop的mode中去,最终在__CFRunLoopDoTimers中进行触发。触发后判断timer是否是repeat的进行移除。
2.perform类型
@interface NSObject (NSDelayedPerforming)
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
@end
此类型和NSTimer类型一样的处理流程。后一个方法是将定时器放在default mode,这就意味着如果后面有滑动的操作,selector的延迟会不准确。因为考虑到准确性可以使用后者,并将mode指定为common mode。
NSObject类里面还声明了以下方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
这些方法最终是转化成了objc_msgSend,和RunLoop无关。
3.CADisplayLink类型
CADisplayLink必须调用-[CADisplayLink addToRunLoop:forMode:]才能生效,最终会通过CFMachPortCreateRunLoopSource来创建一个source1(基于port),添加到指定的RunLoop Mode中去。触发时在__CFRunLoopDoSource1中,通过display_timer_callback被调用。
3.Runloop与performSelectorOnThread
NSThreadPerformAdditions分类中的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg ;
执行后会在目标thread中的RunLoop中添加一个source0,其callback最终会调到selector。当执行performSelector系列时,会在找到这个source0,然后调用最终在__CFRunLoopDoSources0中调用CFRunLoopSourceSignal标记该souece0为ready状态,然后通过CFRunLoopWakeUp唤起目标thread的runloop来处理这个source0,最终selector得以执行。由此可见,若目标线程没有RunLoop则selector不会执行。
4.Runloop的运行
NSRunLoop暴露了3个关于run方法
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
NSRunLoop没有开源,但Swift版本的RunLoop是开源的
public func run() {
while run(mode: .default, before: Date.distantFuture) { }
}
public func run(until limitDate: Date) {
while run(mode: .default, before: limitDate) && limitDate.timeIntervalSinceReferenceDate > CFAbsoluteTimeGetCurrent() { }
}
public func run(mode: RunLoop.Mode, before limitDate: Date) -> Bool {
if _cfRunLoop !== CFRunLoopGetCurrent() {
return false
}
let modeArg = mode._cfStringUniquingKnown
if _CFRunLoopFinished(_cfRunLoop, modeArg) {
return false
}
let limitTime = limitDate.timeIntervalSinceReferenceDate
let ti = limitTime - CFAbsoluteTimeGetCurrent()
CFRunLoopRunInMode(modeArg, ti, true)
CFRunLoopStop(_cfRunLoop)
return true
}
runMode:beforeDate:则直接调用了CFRunLoopRunInMode(),即只运行一次。
其中run是一个while循环,内部不停调用runMode:beforeDate:,当[runMode: beforeDate:]返回NO时,则退出循环,-[NSRunLoop run]后面的代码就会被执行。而[runMode: beforeDate:]返回NO,通常是因为_CFRunLoopFinished返回了YES。当RunLoop里面没有指定的mode,或者对应的mode里面没有timer/source(source0和source1)/block时,就会返回YES。
runUntilDate:和run类似,但调用前会先对limitDate进行判断。
这也是为什么多数情况下 [[NSRunLoop currentRunLoop] run] 执行后,CFRunLoopStop()无法停止RunLoop的原因。而通过CFRunLoopRun()开启的,就可以被CFRunLoopStop()停止。
5.View的更新
UIView的更新包含了内容和布局的更新
主线程的runloop注册了3个observer,一个优先级为1999000,Activity为BeforeWaiting | Exit,回调为_beforeCACommitHandler。
另一个优先级为2001000,Activity也为BeforeWaiting | Exit,回调为_afterCACommitHandler。
还有一个observer,一个优先级为2000000,Activity为BeforeWaiting | Exit,回调为CA::Transaction::observer_callback(__CFRunLoopObserver, unsigned long, void),这个是用来更新内容和布局的。
* frame #0: 0x0000000105e9174a testrl`-[FGView drawRect:](self=0x00007ffbbf107b70, _cmd="drawRect:", rect=(origin = (x = 0, y = 0), size = (width = 10, height = 100))) at FGView.m:34:5
frame #1: 0x00007fff24c17ccd UIKitCore`-[UIView(CALayerDelegate) drawLayer:inContext:] + 625
frame #2: 0x00007fff27a0491d QuartzCore`-[CALayer drawInContext:] + 288
frame #3: 0x00007fff278b3b3d QuartzCore`CABackingStoreUpdate_ + 219
frame #4: 0x00007fff27a0e411 QuartzCore`invocation function for block in CA::Layer::display_() + 53
frame #5: 0x00007fff27a0415a QuartzCore`-[CALayer _display] + 2168
frame #6: 0x00007fff27a17def QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 477
frame #7: 0x00007fff27951f26 QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double, double*) + 656
frame #8: 0x00007fff279893b9 QuartzCore`CA::Transaction::commit() + 713
frame #9: 0x00007fff2798a51f QuartzCore`CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 79
frame #10: 0x00007fff2038fd31 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #11: 0x00007fff2038a542 CoreFoundation`__CFRunLoopDoObservers + 541
frame #12: 0x00007fff2038aaf5 CoreFoundation`__CFRunLoopRun + 1129
以上是一次内容更新的调用堆栈,自定义FGView只重写了方法-[UIView drawRect:]方法。可以看出此次内容的更新是由observer_callback引起的。
* frame #0: 0x000000010739871c testrl`-[FGView layoutSubviews](self=0x00007f8f67e0d4b0, _cmd="layoutSubviews") at FGView.m:16:1
frame #1: 0x00007fff24c18c90 UIKitCore`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2946
frame #2: 0x00007fff27a055b8 QuartzCore`-[CALayer layoutSublayers] + 258
frame #3: 0x00007fff27a0be3f QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 611
frame #4: 0x00007fff27a17c53 QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 65
frame #5: 0x00007fff27951f26 QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double, double*) + 656
frame #6: 0x00007fff279893b9 QuartzCore`CA::Transaction::commit() + 713
frame #7: 0x00007fff2798a51f QuartzCore`CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 79
frame #8: 0x00007fff2038fd31 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #9: 0x00007fff2038a542 CoreFoundation`__CFRunLoopDoObservers + 541
frame #10: 0x00007fff2038aaf5 CoreFoundation`__CFRunLoopRun + 1129
以上是一次布局更新的调用堆栈,自定义FGView只重写了方法-[UIView layoutSubviews:]方法。可以看出此次布局的更新是由observer_callback引起的。
UIView的layout和内容的更新流程见另一片文章UIView和CALayer
6.AutoReleasePool
主线程的RunLoop注册了2个observer,一个优先级为-2147483647(优先级最高),Activity为Entry。一个优先级为2147483647(优先级最低),Activity为BeforeWaiting | Exit。它们的回调为_runLoopObserverCallout。里面进行了以下操作:
Activity是kCFRunLoopEntry时,执行__pushAutoreleasePool,kCFRunLoopBeforeWaiting时 ,先__pushAutoreleasePool,然后再__pushAutoreleasePool。是kCFRunLoopExit,执行__pushAutoreleasePool。
7.手势
主线程的RunLoop注册了一个observer,一个优先级为0,Activity为BeforeWaiting,回调为_UIGestureRecognizerUpdateObserver。
8.事件
SpringBoard捕获事件后,会通过port(source1类型)发消息给com.apple.uikit.eventfetch线程的runloop,这个port对应的回调是__IOHIDEventSystemClientQueueCallback,最终会到UIKitCore框架的-[UIEventDispatcher eventFetcherDidReceiveEvents:]中,根据UIEventDispatcher找到主线程对应的source(source0),设置ready,并唤醒主线程的runloop,这个source对应的回调是__eventFetcherSourceCallback。将事件传递给WIndow进行派发。
source0不能主动唤起RunLoop,只能使用先给source0发信号,即设置标记表示ready,然后唤起runloop以处理该source0。
CFRunLoopSourceSignal(source);
CFRunLoopWakeUp(runloop);
以上基于iOS 14.5系统版本,不同系统版本略有差异。
9 有趣的事情
在按钮的点击事件中,会执行下面代码
- (void)threadDown {
NSLog(@"threadDown");
if (!_thread) {
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEntry) object:nil];
_thread.name = @"myThread";
[_thread start];
// 如果休眠一会,则后面的perform会晚一会执行
sleep(2);
NSLog(@"after sleep");
}
[self performSelector:@selector(threadAgain) onThread:_thread withObject:nil waitUntilDone:NO];
}
- (void)threadEntry {
NSLog(@"threadEntry");
[[NSRunLoop currentRunLoop] run]; //这种的无法stop,只能通过[NSThread exit]了
}
现象是如果休眠2s,新线程执行完threadEntry就退出了,即则不会新线程上执行threadAgain方法。如果不休眠则一切正常。这是为什么呢?
上面我们提到过,performSelectorOnThread系列方法会创建一个source0添到目标线程的RunLoop中去。所以我们在threadEntry直接run就行了。而新线程的初始化方法threadEntry执行的并不是立即执行的,当threadEntry方法执行时,performSelectorOnThread已经执行过了,此时RunLoop里面已经有了source0,所以RunLoop可以运行起来,这样线程就不会退出了。当我们休眠2s后再执行performSelectorOnThread时,则threadEntry已经执行过了,而执行时RunLoop里面没有source和timer,所以run方法会直接退出。然后线程就退出了。