Run Loops 是个什么东西。

-----


写代码总是站在高出去思考什么叫做OO,有意避开却偶尔踩一腿落下时候又会碰到它,也许时而在使用定时器把它扔到池子的时候都不会注意到溅起了浪花。

理解翻译runLoop文档,个人僻陋情怀浓重。

且行且努力吧 :

---

Run Loops

【第一部分】概要:(part1+part2

(一)part1

(你可以把 池子 理解成是一个生态系统,是一个循环。)

1   是与线程广联的一个基础构造设计,所以她作用的对象是线程;(一个池子 唯一对应 一个线程,苹果系统中它们是池子是伴生与线程的)

2   是用于组织规划和协调输入事件的处理池;(如果敲键盘的手是一个线程,那么池子可以理解为手上的神经反射系统)

3   作用就是在有任务的时候保证线程去工作,没有任务的时候保证线程处于休息状态;(合理利用CPU资源,节能,提升程序体验)

(二)part2

1   苹果已经让它处在底层智能运行,但它不是完全自动化的。因此,在一些恰当的场合去设计自己的线程和池子来响应一些输入事件。

2   Cocoa Core Foundation都有提供管理线程的池子对象(NSRunLoop|CFRunLoop),但是不需要去创建池子对象,上文说了,在创建启动线程的时候它已经关联伴生(包括主线程在内的每一个线程都如此),只需要get过来就可以使用了;app框架会在应用程序启动过程中自动初始和运行主线程的池子,所以只有非主线程的池子才会有需要显明的运行调用(而实际的一些技术手段中,比如GCDdispatch_启动的线程,也不需要coder去寻找池子来开启,池子其实尽量保持在皮肤之下最好)。





【第二部分】池子解析(Anatomy of a Run Loop

(一)前言:

*   池子要做的事情是:

    1. 保证线程的生命力(因此对应代码会提供一个while/for 的循环驱动池子,保障线程生命特征,也要在线程寿终正寝的时候告知处理exit)

    2. 处理外部输入事件的响应机制(因此定义了不同事件源如:input sourcesTimer sources,也要创造一些监察使observers来针对处理各种sources,最后成立Modes组织来编排它们的关系)

    3. 线程之间的合作交流。

    4. 这个图//https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/Art/runloop.jpg


(二)池子模式Mode

*   (可理解为池子当前模式)

    1. 模式定义了一种存在关联关系,是sourcesobserver之间的,(就好比我们的感官与这个世间万物:眼、耳、鼻、舌、身、意 对应 色、声、香、味、触、法。它是存在一个对应关系的)并且用名字坐标识(编程语言中的 枚举标识)。

    2. 默认的总是看不出来,每一次启动运行池子,它其实都会有一个特定的模式,无论是Cocoa 还是CFCore Foundation)都会定义默认的一个池子模式以及几个一般有用的池子模式。

    3. 可以自定义Model,但是它内部的一些东西不能少,(sourcestimers, observers)

    4. 定义好了Model,它执行的任务就是对事件进行过滤(基于事件的sources,而不是事件类型),让能够通过Model条件的事件得到线程的处理。

    5. 苹果预定义的池子模式:(着眼当下和未来,不考虑老版本系统了,有人说了,着重关注一下前三个模式)

        Default         :   NSDefaultRunLoopMode (Cocoa) | kCFRunLoopDefaultMode (Core Foundation)

        Event tracking  :   NSEventTrackingRunLoopMode (Cocoa)

        Common modes    :   NSRunLoopCommonModes (Cocoa) | kCFRunLoopCommonModes (Core Foundation)

        Connection      :   NSConnectionReplyMode (Cocoa)

        Modal           :   NSModalPanelRunLoopMode (Cocoa)



(三)输入源(Input Sources

*   输入源会悄悄的(异步-暗度陈仓)的给线程传递事件,它一般分为两类了:基于端口的和自定义的,这两类输入源的主要区别是信号,端口源的信号是内核自动处理,而自定义源的型号需要从另一个线程中手动处理。大多数时候,池子的Mode都是默认的,当时支持自定义,当创建一个输入源,可以将池子的一个或多个Modesource设置为它的值,然后池子就只处理池子中当前Mode的关联事件。


*   基于端口的源(Port-Based Sources

    Cocoa CF 内建支持有创建端口源的对象和函数方法,如 Cocoa 中根本不需直接创建输入源,只需要利用NSPort的方法去创建一个端口对象添加到池子中,通过该端口对象来操纵设置需要的输入源。 但是在 CF 中,则需要同时创建端口和池子的源,然后使用函数方法将端口和事件源简历联系,得到一个适当的关联对象(基于端口的源)。


*   自定义输入源/用户输入源(Custom Input Sources)

    创建一个自定义输入源,需要关联 CF 中的 CFRunLoopSourceRef,也要定义事件信号传递的机制。


*   Cocoa 执行选择源(Cocoa Perform Selector Sources

    除了端口源,Cocoa 定义一个用户输入源可以在任何线程上perform a selctor(PSEL) 和端口源一样的是,PSEL是目标线程中的一个序列,这样可以缓解大量方法在一个线程同步执行的情况,同端口源不同,一个PSEL的源会在执行了Selector后将自己从池子中移除。执行Selector的目标线程需要有一个激活的池子,如果是自建的线程就需要显明的启动池子,主线程的会自己启动。PSEL的方法在NSObject类中有接口。它们不创建线程,只是通知池子好使的线程执行任务(执行实际是在池子的下一个循环中)。

        performSelectorOnMainThread:withObject:waitUntilDone:

        performSelectorOnMainThread:withObject:waitUntilDone:modes:

        performSelector:onThread:withObject:waitUntilDone:

        performSelector:onThread:withObject:waitUntilDone:modes:

        performSelector:withObject:afterDelay:

        performSelector:withObject:afterDelay:inModes:

        cancelPreviousPerformRequestsWithTarget:

        cancelPreviousPerformRequestsWithTarget:selector:object:


(四)时钟源(Timer Sources

*   始终源会在一个预设在未来的时间点给线程异步传递事件。它是线程自我提醒做某事的方法。虽然它生成了一个基于时间的通知,不过时钟并不是真正的时间进程机制,和输入源一样时钟源是与池子中特定的Mode联系在一起的。如果时钟不在池子当前监听的Mode中,将无效,直到池子运转到支持时钟源的Mode时生效。时钟源起作用的前提就是池子当前Mode需要支持时钟,并且池子在活动状态。设置时钟的时间段是计划事件段,不是自然时间段,错过了就会继续再按照计划的时间段进行计时,而不是以自然时间段中的铁定时间点作为到点机制(事件段为5s,自然时间段过了20s,而时钟其实走了三次,且可能对应自然时间是18s)。


(五)运行池观察者(Run Loop Observers

*   相比较之下,源(sources)是在一个同步或异步的事件恰当发生(遵循定义的)时候启动,池子观察者会在池子自己运行时候在一些特别指定的位置启动。可以用它为线程准备事件,

    可以将它与一些事件关联:The entrance to the run loop.| When the run loop is about to process a timer.| When the run loop is about to process an input source.| When the run loop is about to go to sleep.| When the run loop has woken up, but before it has processed the event that woke it up.| The exit from the run loop.

    apps添加池子观察着需要用 CF 。创建一个新的池子观察者,需要穿件一个  CFRunLoopObserverRef的实例。与时钟相似的是,观察者可以重复使用。


(六)池子事件序列(The Run Loop Sequence of Events

*   线程池子启动,线程会根据事件为任何附属上的观察者生成通知或者执行一些待定事件,这个过程有一个特别的事件处理序列:

    1. 通知观察者池子准备好了

    2. 通知观察者任何准备触发的时钟

    3. 通知观察者非端口输入源准备触发

    4. 触发任何准备触发的非端口输入源

    5. 如果一个端口输入源准备就绪并等待着触发,事件进程直接跳到第9步。

    6. 通知观察者线程准备休眠

    7. 让线程休眠直到出现可唤醒线程的事件:(一个端口输入源事件抵达|触发一个时钟|池子设定的超时时间到|池子被显明唤醒)

    8. 通知观察者线程醒来

    9. 执行待定事件:a.如果用户时钟被触发,执行时钟时间并重新让池子进入第2步;b.如果输入源被触发,传递事件;c.如果池子意外唤醒但是还没有超时,重新让池子进入第2步。

    10.通知观察者,池子已经退出。

*   对输入源和时钟的通知和事件的实际执行时不同步的,中间必然有一定的时间差,如果对时间要求比较苛刻,可是使用睡眠和唤醒通知来帮助关联定时和实际事件。





【第三部分】 什么时候使用池子(When Would You Use a Run Loop

*   只有在为应用程序创建新的线程的时候,才会需要去显明的运行一个池子。主线程是应用程序的核心,主线程的池子也非常重要,所以苹果框架提供了代码让主线程的池子自动开启运行,运行方法已经作为程序启动序列中的一部分。如果使用Xcode模板工程创建应用,不要也没有必要去折腾着挑战惯例。

*   如果是其它线程,确定一下是否需要运行池,需要的话则自己设置它。在所有的情况中都不用去启动一个线程的池子,池子的重心在于可能与线程有更多交互的情形,需要启动池子的情况列举如下:1.使用端口源或用户输入源与其它线程进行通讯;|2.在线程中使用到时钟;| 3.Cocoa 程序中使用到PSELperformSelector)方法;|4.维持线程生命周期去处理一个周期性的任务。




【第四部分】使用池子对象(Using Run Loop Objects


*   池子对象为池子和池子的使用提供了添加输入源,时钟,和池子观察者的主要接口。每一个线程都有自己唯一的一个池子对象,在Cocoa中,池子对象实例是一个NSRunLoop类,在底层一点的应用中,会是一个CFRunLoopRef类型的指针。


(一)获取池子对象(Getting a Run Loop Object

*   从当前线程获取池子对象的方法:

    1. Cocoa 应用程序中,使用NSRunLoop的类方法currentRunLoop

    2. 也可以使用CFRunLoopGetCurrent 方法

*   虽然二者不是无缝桥接的(Toll-Free bridged)类型的,但是NSRunLoop有一个getCFRunLoop的方法返回CFRunLoopRef类型,两种对象引用的同一个池子,所以可以甚至根据需要混杂使用它们。


(二) 配置池子(Configuring the Run Loop

*   在新线程中运行池子,至少也得添加一个输入源或者时钟给它,如果池子没有任何监听的源,一旦运行就会马上退出。除了导入源之外,也需要导入池子观察者,并用它们监听池子的不同执行阶段,观察者类型使用 CFRunLoopObserverRef,添加方法使用CFRunLoopAddObserver,观察者只能用CF来创建。

//

- (void)threadMain

{

    // The application uses garbage collection, so no autorelease pool is needed.

    //获取当前线程的池子

    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];


    // Create a run loop observer and attach it to the run loop.  使用 CF,上下文环境创建观察者

    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};

    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,

                                                               kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);


    if (observer)

    {

        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];//池子对象的 Cocoa CF

        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);//添加

    }


    // Create and schedule the timer. //为了不让池子立刻退出,添加了一个新的轮询机制(时钟轮询)

    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self

                                   selector:@selector(doFireTimer:) userInfo:nil repeats:YES];


    NSInteger    loopCount = 10;

    do

    {

        // Run the run loop 10 times to let the timer fire.

        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];

        loopCount--;

    }

    while (loopCount);

}


(三)开启池子(Starting the Run Loop

*   在新线程中运行池子,至少也得添加一个输入源或者时钟给它,如果池子没有任何监听的源,一旦运行就会马上退出。下面列举了一些开启池子的方法:

    1.Unconditionally |2.With a set time limit | 3.In a particular mode

*   第一种方式进入池子是最简单和最不理想的方法,这种方式下线程将进入一个永久循环,对池子的可执行操作很少,虽然可以添加和一处输入源和时钟,但是停止池子的唯一方法是杀死它,并且池子无法使用用户Mode

*   对比第一种,设置时间限定方法会好很多,在设定时间内,池子保持运行,如果有事件抵达,为任务事件被派遣到一个执行操作,然后池子退出,这时可以重新启动池子去处理下一个事件;如果超时了,也可以简单的重启池子或者在这段时间处理别的事情。

*   使用指定Modes启动池子,在启动池子的时候,可以同超时设置一起使用,Modes限制了给池子床底事件的源的类型,

- (void)skeletonThreadMain

{

    // Set up an autorelease pool here if not using garbage collection.

    BOOL done = NO;


    // Add your sources or timers to the run loop and do any other setup.


    do

    {

        // Start the run loop but return after each source is handled.

        SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);


        // If a source explicitly stopped the run loop, or if there are no

        // sources or timers, go ahead and exit.

        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))

            done = YES;


        // Check for any other exit conditions here and set the

        // done variable as needed.

    }

    while (!done);


    // Clean up code here. Be sure to release any allocated autorelease pools.

}

*   使用相似的手法,可以使用CocoaNSRunLoop类去运行一个池子,


(四)退出池子(Exiting the Run Loop

*   两种方法能够在执行事件之前将池子退出。

    1. 设置池子的超时时间,在该时间内运行到时就会退出

    2. 直接告诉池子,stop

*   如果能够确认所有的任务完成了,包括能够在退出之前给池子观察者传递通知,那么超时方法是首选的。

*   使用 CFRunLoopStop 函数结束池子与超时方法与电箱,池子会发出所有保留的池子通知之后才退出,不同之处是可以在unconditionally方式启动池子的情况下用这个技术。

*   移除池子输入源和时钟也可能导致池子退出,但是这种方法不是退出池子的可靠方法,有的系统机制添加的输入源会让池子持有需要的事件,而代码中并不一定知道这些源,可能无法移除,它们就是为了保证池子不退出的。


(五)线程安全 池子对象(Thread Safety and Run Loop Objects

*   线程安全跟操作池子的API有关,CF 的函数方法通常是线程安全的,可以从任何线程中调用。

*   不同CFCocoa NSRunLoop类,不是线程安全的。如果使用它来改变一个池子,应该只在持有改池子的同一个线程中操作。对属于不同显示的池子添加输入源或者时钟,会导致代码奔溃或者意想不到的结果。









【第五部分】 配置 池子源(Configuring Run Loop Sources

*   下面的单元将使用Cocoa CF 的方法 示例展示怎样设置不同的输入源,


----- 定义一个用户输入源(Defining a Custom Input Source

*   创建用户输入源需要注意几个概念

    1. 想要输入源针对应用程序的信息;

    2. 一个让用户有兴趣知道怎样发生输入源操作的方案例程;

    3. 一个处理任何用户发送问题的程序例程

    4. 一个取消输入源的取消例程


这里有张图//https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/Art/custominputsource.jpg


*   定义输入源(Defining the Input Source

    定义用户输入源需要用CF的例程设置池子源并关联它们。基本操作都是 C 的函数,不妨碍使用 OC C++ 来实现。



结语:

1. 翻译到这个地方,池子是个什么东西,池子更苹果程序的关系,池子和线程为嘛那么亲密,池子又为何那么沉默在皮肤之下却是一个app运行的核心设计模式之一,可能每一次看文档之后都会有心的不同的理解,过不久可能又会忘却一些文字概念上的东西,其实文字是啥不重要,重要的是文字传达的意思,所以中文英文什么文字不是很重要,记不住也不重要。核心就是它的原理和逻辑搞明白了,正所谓明理自骄,不在豪华。

2. 本文是常识性注入理解的翻译,是一个容错的学习过程,走一朝总会有一些好处,起码在理解上。

3. 不是说实现不重要,特别对于一个coder来说,逻辑原理背后的code实现才是coding工作中的核心,总之循序渐进,如果不理解它是个什么玩意儿,很难跟它玩到一块儿的。

4. 苹果文档本身就是一份最好的学习资料,先做理解。

5. 这里是一片非常详细的池子的技术讲解:深入理解RunLoop here you are

6. 这里是苹果文档:apple say: run loop, go ~







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值