runloop 详解

RunLoop  
 相信大家从它的名字就可以看得出来就是一个循环,我感觉想学好一门语言就应该从深层次理解它,以后你学习其他一种语言就会发现,其实语言都是共通的,只要你理解了这种思想,学习起来其他语言机会很容易.不知道大家有没有想过,一个应用程序都是从main 函数开始的,但是oc  main 函数里只有一句代码 int main(int argc, char *argv[])

     {

            @autoreleasepool {

              return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));

           }

  }
那么一个程序运行了之后 即使你不执行任何操作,它也不会退出,但是当你操作的时候比如点击事件,程序就会马上做出响应,给我们一种随时待命的感觉,在没人操作的时候它就一直待命,让它工作的时候,它就能立刻响应.其实,这就是runloop 的作用
一. runloop 和线程
先来说说线程,举个形象的比喻,有些线程就好像一条直线,程序执行完了,线程就结束了,但是有些线程不是这样的,有些线程像圆形一样,他可以不断地循环,直到人为的通过某种方式使他结束,那么这种线程就是通过runloop 来实现的
run  表示运行 loop 就是循环的意思,合起来就是运行着的循环,实际上线程和runloop 是密切相关的,runloop 是为了线程而生,没有线程它就没有存在的必要,每个线程 包括主线程都有与之对应的runloop对象
主线程的runloop 是默认启动的,重点是UIApplication函数,这个方法会为man thread 设置一个NSRunloop对象,这就解释了为什么没人操作它,他却能休息,需要的时候又能够立刻响应  对于其他线程来说,runloop 是默认不会启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已经确定的任务则不需要  在任何一个Cocoa程序的线程中,都可以通过:

NSRunLoop   *runloop = [NSRunLoop currentRunLoop];

来获取到当前线程的run loop

cocoa 中的 NSRunloop  类并不是线程安全的  我们不能在一个线程中去操作另外一个线程的runloop对象,runloop 同时也负责autoreleasePool的创建和释放,每当一个运行循环结束时,它都会释放一次autoreleasePool ,同时池中的所有自动释放的变量都会被释放掉

runloop 的优点\

NSRUNLOOP 是一个更加高明的消息处理机制,它对消息处理进行了更好的抽象和封装,其次很重要的是 使用runloop的时候可以使你的线程在有工作的时候工作 没有工作的时候休眠,这就大大的节省了系统资源

runloop 接受事件来自两个不同的来源 输入源 (input source )和定时源 (time source )


需要说明的是,当你创建输入源,你需要将其分配给run loop中的一个或多个模式(什么是模式,下文将会讲到)。模式只会在特定事件影响监听的源。大多数情况下,run loop运行在默认模式下,但是你也可以使其运行在自定义模式。若某一源在当前模式下不被监听,那么任何其生成的消息只在run loop运行在其关联的模式下才会被传递。

图-1  Runloop的结构和输入源类型

 

2.1.1输入源(input source)

传递异步事件,通常消息来自于其他线程或程序。输入源传递异步消息给相应的处理例程,并调用runUntilDate:方法来退出(在线程里面相关的NSRunLoop对象调用)。

2.1.1.1基于端口的输入源

基于端口的输入源由内核自动发送。

Cocoa和Core Foundation内置支持使用端口相关的对象和函数来创建的基于端口的源。例如,在Cocoa里面你从来不需要直接创建输入源。你只要简单的创建端口对象,并使用NSPort的方法把该端口添加到run loop。端口对象会自己处理创建和配置输入源。

在Core Foundation,你必须人工创建端口和它的run loop源。我们可以使用端口相关的函数(CFMachPortRef,CFMessagePortRef,CFSocketRef)来创建合适的对象。下面的例子展示了如何创建一个基于端口的输入源,将其添加到run loop并启动:

voidcreatePortSource()

{

    CFMessagePortRef port = CFMessagePortCreateLocal(kCFAllocatorDefaultCFSTR("com.someport"),myCallbackFuncNULLNULL);

    CFRunLoopSourceRef source =  CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, port, 0);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

    while (pageStillLoading) {

        NSAutoreleasePool *pool = [[NSAutoreleasePool allocinit];

        CFRunLoopRun();

        [pool release];

    }

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

    CFRelease(source);

}

2.1.1.2自定义输入源

自定义的输入源需要人工从其他线程发送。

为了创建自定义输入源,必须使用Core Foundation里面的CFRunLoopSourceRef类型相关的函数来创建。你可以使用回调函数来配置自定义输入源。Core Fundation会在配置源的不同地方调用回调函数,处理输入事件,在源从run loop移除的时候清理它。

除了定义在事件到达时自定义输入源的行为,你也必须定义消息传递机制。源的这部分运行在单独的线程里面,并负责在数据等待处理的时候传递数据给源并通知它处理数据。消息传递机制的定义取决于你,但最好不要过于复杂。创建并启动自定义输入源的示例如下:

voidcreateCustomSource()

{

    CFRunLoopSourceContext context = {0NULLNULLNULLNULLNULLNULLNULLNULLNULL};

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault0, &context);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

    while (pageStillLoading) {

        NSAutoreleasePool *pool = [[NSAutoreleasePool allocinit];

        CFRunLoopRun();

        [pool release];

    }

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

    CFRelease(source);

}

2.1.1.3Cocoa上的Selector源

除了基于端口的源,Cocoa定义了自定义输入源,允许你在任何线程执行selector方法。和基于端口的源一样,执行selector请求会在目标线程上序列化,减缓许多在线程上允许多个方法容易引起的同步问题。不像基于端口的源,一个selector执行完后会自动从run loop里面移除。

当在其他线程上面执行selector时,目标线程须有一个活动的run loop。对于你创建的线程,这意味着线程在你显式的启动run loop之前是不会执行selector方法的,而是一直处于休眠状态。

NSObject类提供了类似如下的selector方法:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;

 

2.1.2定时源(timer source)

定时源在预设的时间点同步方式传递消息,这些消息都会发生在特定时间或者重复的时间间隔。定时源则直接传递消息给处理例程,不会立即退出run loop。

需要注意的是,尽管定时器可以产生基于时间的通知,但它并不是实时机制。和输入源一样,定时器也和你的run loop的特定模式相关。如果定时器所在的模式当前未被run loop监视,那么定时器将不会开始直到run loop运行在相应的模式下。类似的,如果定时器在run loop处理某一事件期间开始,定时器会一直等待直到下次run loop开始相应的处理程序。如果run loop不再运行,那定时器也将永远不启动。

创建定时器源有两种方法,

方法一:

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:4.0

                                                     target:self

                                                   selector:@selector(backgroundThreadFire:) userInfo:nil

                                                    repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:timerforMode:NSDefaultRunLoopMode];

 

方法二:

[NSTimer scheduledTimerWithTimeInterval:10

                                        target:self

                                       selector:@selector(backgroundThreadFire:)

                                       userInfo:nil

                                       repeats:YES];

2.2 RunLoop观察者

源是在合适的同步或异步事件发生时触发,而run loop观察者则是在run loop本身运行的特定时候触发。你可以使用run loop观察者来为处理某一特定事件或是进入休眠的线程做准备。你可以将run loop观察者和以下事件关联:

1.  Runloop入口

2.  Runloop何时处理一个定时器

3.  Runloop何时处理一个输入源

4.  Runloop何时进入睡眠状态

5.  Runloop何时被唤醒,但在唤醒之前要处理的事件

6.  Runloop终止

和定时器类似,在创建的时候你可以指定run loop观察者可以只用一次或循环使用。若只用一次,那么在它启动后,会把它自己从run loop里面移除,而循环的观察者则不会。定义观察者并把它添加到run loop,只能使用Core Fundation。下面的例子演示了如何创建run loop的观察者:

- (void)addObserverToCurrentRunloop

{

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

    NSRunLoop*myRunLoop = [NSRunLoop currentRunLoop];

   

    // Create a run loop observer and attach it to the runloop.

    CFRunLoopObserverContext  context = {0selfNULLNULLNULL};

   CFRunLoopObserverRef    observer =CFRunLoopObserverCreate(kCFAllocatorDefault,

                                                              kCFRunLoopBeforeTimers, YES0, &myRunLoopObserver, &context);

   

    if (observer)

    {

        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];

       CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);

    }

}

其中,kCFRunLoopBeforeTimers表示选择监听定时器触发前处理事件,后面的YES表示循环监听。

 

2.3 RunLoop的事件队列

每次运行run loop,你线程的run loop对会自动处理之前未处理的消息,并通知相关的观察者。具体的顺序如下:

  1. 通知观察者run loop已经启动
  2. 通知观察者任何即将要开始的定时器
  3. 通知观察者任何即将启动的非基于端口的源
  4. 启动任何准备好的非基于端口的源
  5. 如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤9。
  6. 通知观察者线程进入休眠
  7. 将线程置于休眠直到任一下面的事件发生:
    • 某一事件到达基于端口的源
    • 定时器启动
    • Run loop设置的时间已经超时
    • run loop被显式唤醒
  8. 通知观察者线程将被唤醒。
  9. 处理未处理的事件
    • 如果用户定义的定时器启动,处理定时器事件并重启run loop。进入步骤2
    • 如果输入源启动,传递相应的消息
    • 如果run loop被显式唤醒而且时间还没超时,重启run loop。进入步骤2
  10. 通知观察者run loop结束。

因为定时器和输入源的观察者是在相应的事件发生之前传递消息,所以通知的时间和实际事件发生的时间之间可能存在误差。如果需要精确时间控制,你可以使用休眠和唤醒通知来帮助你校对实际发生事件的时间。

因为当你运行run loop时定时器和其它周期性事件经常需要被传递,撤销run loop也会终止消息传递。典型的例子就是鼠标路径追踪。因为你的代码直接获取到消息而不是经由程序传递,因此活跃的定时器不会开始直到鼠标追踪结束并将控制权交给程序。

Run loop可以由run loop对象显式唤醒。其它消息也可以唤醒run loop。例如,添加新的非基于端口的源会唤醒run loop从而可以立即处理输入源而不需要等待其他事件发生后再处理。

从这个事件队列中可以看出:

①如果是事件到达,消息会被传递给相应的处理程序来处理, runloop处理完当次事件后,run loop会退出,而不管之前预定的时间到了没有。你可以重新启动run loop来等待下一事件。

②如果线程中有需要处理的源,但是响应的事件没有到来的时候,线程就会休眠等待相应事件的发生。这就是为什么run loop可以做到让线程有工作的时候忙于工作,而没工作的时候处于休眠状态。

 

2.4什么时候使用run loop

仅当在为你的程序创建辅助线程的时候,你才需要显式运行一个run loop。Run loop是程序主线程基础设施的关键部分。所以,Cocoa和Carbon程序提供了代码运行主程序的循环并自动启动run loop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)作为程序启动步骤的一部分,它在程序正常启动的时候就会启动程序的主循环。类似的,RunApplicationEventLoop函数为Carbon程序启动主循环。如果你使用xcode提供的模板创建你的程序,那你永远不需要自己去显式的调用这些例程。

对于辅助线程,你需要判断一个run loop是否是必须的。如果是必须的,那么你要自己配置并启动它。你不需要在任何情况下都去启动一个线程的run loop。比如,你使用线程来处理一个预先定义的长时间运行的任务时,你应该避免启动run loop。Run loop在你要和线程有更多的交互时才需要,比如以下情况:

  1. 使用端口或自定义输入源来和其他线程通信
  2. 使用线程的定时器
  3. Cocoa中使用任何performSelector…的方法
  4. 使线程周期性工作

如果你决定在程序中使用run loop,那么它的配置和启动都很简单。和所有线程编程一样,你需要计划好在辅助线程退出线程的情形。让线程自然退出往往比强制关闭它更好。

  -------- by heartFlyings













  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值