RunLoop的一些理解

1.RunLoop与dispatch的关系

1.在__CFRunLoopRun函数中,用dispatch_source_create创建一个定时器。处理此次runLoop mode的运行时间,唤醒runloop。
2.使用dispatch执行的block(main_queue中),例如
dispatch_async 1,异步提交的任务
dispatch_after 2,提交的延时任务
dispatch_source_create
dispatch_source_set_timer
dispatch_source_set_event_handler
3,设置的定时器

它们的执行流程是
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

以上仅指main_queue

2.Runloop与定时器的关系

dispatch类型的定时器,上面已经谈过了,我们谈下其他类型的定时器,

1.NSTimer类型的

这种类型会转换为CFRunLoopTimerRef,然后加入到RunLoop的mode中去,最终在__CFRunLoopDoTimers中进行触发。触发后判断timer是否是repeat的进行移除。

2.perform类型
-[NSObject performSelector:withObject:afterDelay:]
-[NSObject performSelector:withObject:afterDelay:inModes:]

此类型和NSTimer类型一样的处理流程。前者是将定时器放在default mode,这就意味着如果后面有滑动的操作,selector的延迟会不准确。因为考虑到准确性可以使用后者,并将mode指定为common mode。

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<NSString *> *)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<NSString *> *)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是__NSThreadPerformPerform,在里面使用performSelector:withObject:来调到selector。具体就是当执行performSelector:onThread系列方法时,会执行thread的-[NSThread _nq:]方法,在里面添加source0,设置为ready并唤醒RunLoop,来处理source0,由此可见,若目标线程没有RunLoop则selector不会执行。

4.Runloop的运行

NSRunLoop暴露了3个关于run方法

- (void)run; 
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;

runMode:beforeDate:则直接调用了CFRunLoopRunInMode(),即只运行一次,会将limitDate与当前时间的时间差作为参数传入。
runUntilDate:是一个while循环,不停的调用runMode:beforeDate:。但调用前会有一个date的判断。
其中run是一个while循环,内部不停调用runMode:beforeDate:,date参数是 Date.distantFuture。
这也是为什么 [[NSRunLoop currentRunLoop] run] 执行后,CFRunLoopStop()无法停止RunLoop的原因。而通过CFRunLoopRun()开启的,就可以被CFRunLoopStop()停止。
RunLoop运行任何一个mode时,都至少需要有port(source1)/timer/source0。仅有observer是不会运行的。

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*),这个是用来更新内容和布局的,BeforeSource之后会处理source0,通常是触摸事件,我们会进行相应做出UI的改动,比如颜色/位置等,然后BeforeWaiting。来触发Layout和Display。

 * 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时 ,先__popAutoreleasePool,然后再__pushAutoreleasePool。是kCFRunLoopExit,执行__popAutoreleasePool。

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已经执行过了,此时_thread这个线程的RunLoop里面已经有了source0,所以RunLoop可以运行起来,这样线程就不会退出了。当我们休眠2s后再执行performSelectorOnThread时,则threadEntry已经执行过了,而执行时RunLoop里面没有source和timer,所以run方法会直接退出(while 仅循环一次)。然后线程就退出了。


extension 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)
        return true
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值