JHHK

欢迎来到我的个人网站
行者常至 为者常成

参考:RunLoop(二)

参考书籍 《多线程编程指南》
原著:Apple Inc.
翻译:謝業蘭 【老狼】

目录

何时使用RunLoop

iOS程序中 UIApplication 的 run 方法(或 Mac OS X 中的 NSApplication)作为程序启动步骤的一部分,它在程序正常启动的时 候就会启动程序的主循环。类似的,RunApplicationEventLoop 函数为 Carbon 程序 启动主循环。如果你使用 xcode 提供的模板创建你的程序,那你永远不需要自己去显 式的调用这些例程。

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

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

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

使用RunLoop对象

Run loop 对象提供了添加输入源,定时器和 run loop 的观察者以及启动 run loop 的接口。每个线程都有唯一的与之关联的 run loop 对象。在 Cocoa 中,该对象是 NSRunLoop 类的一个实例;而在 Carbon 或 BSD 程序中则是一个指向 CFRunLoopRef 类 型的指针。

一、获得 Run Loop 对象

CFRunLoopRef currentLoopRef = CFRunLoopGetCurrent();
CFRunLoopRef mainLoopRef = CFRunLoopGetMain();

NSRunLoop * currentLoop = [NSRunLoop currentRunLoop];
NSRunLoop * mainLoop = [NSRunLoop mainRunLoop];
CFRunLoopRef currentLoopRef2 = [currentLoop getCFRunLoop];

二、配置 Run Loop
当你在辅助线程运行 run loop 之前,你必须至少添加一输入源或定时器给它。 如果 run loop 没有任何源需要监视的话,它会在你启动之际立马退出。关于如何添加源到 run loop 里面的例子,参阅”配置 Run Loop 源”。

[[[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil] start];

- (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.
    CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, &context);
    if (observer){
        CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
    
    
    // Create and schedule the timer.
    //如果没有这个定时器 loop会启动后立马退出,并且通知也不会发出
    [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--;
        NSLog(@"while循环-%d",loopCount);

    }while (loopCount);
}

-(void)doFireTimer:(id)obj{
    NSLog(@"log: %s",__func__);
}

三、启动 Run Loop
启动 run loop 只对程序的辅助线程有意义。一个 run loop 通常必须包含一个输 入源或定时器来监听事件。如果一个都没有,run loop 启动后立即退出。
有几种方式可以启动 run loop,包括以下这些:

  • 无条件的

无条件的进入 run loop 是最简单的方法,但也最不推荐使用的。因为这样会使 你的线程处在一个永久的循环中,这会让你对 run loop 本身的控制很少。你可以添 加或删除输入源和定时器,但是退出 run loop 的唯一方法是杀死它。没有任何办法可以让这 run loop 运行在自定义模式下。

[myRunLoop run];
  • 设置超时时间

替代无条件进入 run loop 更好的办法是用预设超时时间来运行 run loop,这样 run loop 运作直到某一事件到达或者规定的时间已经到期。如果是事件到达,消息 会被传递给相应的处理程序来处理,然后 run loop 退出。你可以重新启动 run loop 来等待下一事件。如果是规定时间到期了,你只需简单的重启 run loop 或使用此段 时间来做任何的其他工作。

[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
  • 特定的模式

除了超时机制,你也可以使用特定的模式来运行你的 run loop。模式和超时不是互斥的,他们可以在启动 run loop 的时候同时使用。模式限制了可以传递事件给 run loop 的输入源的类型,这在”Run Loop 模式”部分介绍。

[myRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];

列表描述了线程的主要例程的架构。本示例的关键是说明了 run loop 的基本结构(运行原理)

- (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.
    
    
}

四、退出 Run Loop
有两种方法可以让 run loop 处理事件之前退出:

  • 给 run loop 设置超时时间
  • 通知 run loop 停止

如果可以配置的话,推荐使用第一种方法。指定一个超时时间可以使 run loop 退出前完成所有正常操作,包括发送消息给 run loop 观察者。

使用 CFRunLoopStop 来显式的停止 run loop 和使用超时时间产生的结果相似。 Run loop 把所有剩余的通知发送出去再退出。与设置超时的不同的是你可以在无条 件启动的 run loop 里面使用该技术。

尽管移除 run loop 的输入源和定时器也可能导致 run loop 退出,但这并不是可 靠的退出 run loop 的方法。一些系统例程会添加输入源到 run loop 里面来处理所需 事件。因为你的代码未必会考虑到这些输入源,这样可能导致你无法没从系统例程中移除它们,从而导致退出 run loop。

五、线程安全和 Run Loop 对象

runloop和线程

每条线程都有唯一的一个与之对应的RunLoop对象

RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value

线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建

RunLoop会在线程结束时销毁

主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

线程安全

线程是否安全取决于你使用那些 API 来操纵你的 run loop。Core Foundation 中 的函数通常是线程安全的,可以被任意线程调用。但是如果你修改了 run loop 的配 置然后需要执行某些操作,任何时候你最好还是在 run loop 所属的线程执行这些操 作。

至于 Cocoa 的 NSRunLoop 类则不像 Core Foundation 具有与生俱来的线程安全性。 如果你想使用 NSRunLoop 类来修改你的 run loop,你应用在 run loop 所属的线程里 面完成这些操作。给属于不同线程的 run loop 添加输入源和定时器有可能导致你的 代码崩溃或产生不可预知的行为。


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.