RunLoop

作者: HelloEverything | 来源:发表于2017-02-22 17:10 被阅读39次

    http://www.cocoachina.com/ios/20150601/11970.html

    一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

    CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
    Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

    Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

    上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

    你只能通过 mode name 来操作内部的 mode,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。

    苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操作其对应的 Mode。

    同时苹果还提供了一个操作 Common 标记的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用这个字符串来操作 Common Items,或标记一个 Mode 为 "Common"。使用时注意区分这个字符串和其他 mode name。

    总结来说,runMode:beforeDate:
    表示的是 runloop 的单次调用,另外两者则是循环调用。


    总的来说,如果你还想从 runloop 里面退出来,就不能用 run
    方法。根据实践结果和文档,另外两种启动方法也无法手动退出。

    这里其实不正确**runMode:beforeDate: 这个方法开始的 runloop 其实是可以手动停止的
    **

    CFRunLoopStop() 方法只会结束当前的 runMode:beforeDate: 调用,而不会结束后续的调用。

    当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。

    当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

    RunLoop 启动前内部必须要有至少一个 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先创建了一个新的 NSMachPort 添加进去了。通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并在外部线程通过这个 port 发送消息到 loop 内;但此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息。

    GCD不受RunLoop影响,
    普通计时器默认是被添加到 runloop 的NSDefaultRunLoopMode中的,所以当用户进行滑动操作的时候 runloop 切换到UITrackingRunLoopMode,定时器不在被执行

    static 的生命周期是整个工程结束,如果在方法中声明那么作用域会变小,但是不影响其生命周期

    这里的 emptyPort 用来维持 runloop 的运行,根据官方文档的描述,如果 runloop 中没有任何 modeItem,就不会启动,而是立刻退出。之所以选择作为属性而不是临时变量,是因为我发现每次调用 [NSMachPort port] 方法都会占用内存,原因暂时不清楚。

    NSRunLoop主要是用于objective-c程序,而CFRunLoop主要用于C/C++程序,这是因为C/C++程序无法使用objective-c对象而创建的一个类。

    **** 所有线程都自动创建一个RunLoop, 在线程内通过 [NSRunLoop currentRunLoop] 获得当前线程的RunLoop.****






    source0:非基于端口的源
    只包含一个回调(函数指针),他不能主动触发事件


    source1:
    被用于通过内核与其他线程相互发送消息,能够主动唤醒 RunLoop 的线程

    触摸, 锁屏, 摇晃 等用户交互事件


    [图片上传中。。。(1)]

    当在其他线程上面执行selector时,目标线程须有一个活动的run loop。

    cancelPreviousPerformRequestsWithTarget:

    cancelPreviousPerformRequestsWithTarget:selector:object:

    1.通知观察者run loop已经启动
    2.通知观察者任何即将要开始的定时器
    3.通知观察者任何即将启动的非基于端口的源
    4.处理 ( source0 )

    5.如果 source1 准备好并处于等待状态,立即启动;并进入步骤9。
    6.通知观察者线程进入休眠
    7.将线程置于休眠直到任一下面的事件发生:某一事件到达基于端口的源
    定时器启动
    Run loop设置的时间已经超时
    run loop被显式唤醒

    8.通知观察者线程将被唤醒。
    9.处理未处理的事件如果用户定义的定时器启动,处理定时器事件并重启run loop。进入步骤2
    如果输入源启动,传递相应的消息
    如果run loop被显式唤醒而且时间还没超时,重启run loop。进入步骤2

    10.通知观察者run loop结束。

    Q1: 添加到滑动 Model 中的计时器 普通状态下也走

    Q2:

    2017.2.23号重新学习记录

    • 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

    • 一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

    • 这里的 emptyPort 用来维持 runloop 的运行,根据官方文档的描述,如果 runloop 中没有任何 modeItem,就不会启动,而是立刻退出

    • 这里有个概念叫 "CommonModes":一个 Mode 可以将自己标记为"Common"属性(通过将其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 标记的所有Mode里。

    //将 mode 标记为为 "Common"属性
    CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
    
    • "commonModeItems" 被 RunLoop 自动更新到所有具有"Common"属性的 Mode 里去。但是两个被标记为"Common"的 mode 里面的 item 不会互相同步

    • 如何切换 runloop 的运行 mode
      1.先停止 RunLoop

    CFRunLoopStop([runloop getCFRunLoop]);
    

    2.再继续,此时切换线程

        [runloop runMode:@"customMode" beforeDate:[NSDate distantFuture]];
    

    线程通讯 NSMachPort

    • Thread ----> RunLoop ----> MachPort
    • 想要给某个线程发送消息,需要使用该线程的 MachPort
    [ remotePort sendBeforeDate:[NSDate date] msgid:kMsg1 components:array from:myPort reserved:0];
        [remotePort sendBeforeDate:<#(nonnull NSDate *)#> components:<#(nullable NSMutableArray *)#> from:<#(nullable NSPort *)#> reserved:<#(NSUInteger)#>]
    

    相关文章

      网友评论

          本文标题:RunLoop

          本文链接:https://www.haomeiwen.com/subject/gvgywttx.html