Runloop的概念和总结

RunLoop 的概念
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

这种模型通常被称作 Event Loop。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。

所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。
CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

CFRunLoopRef 的代码是开源的,你可以在这里 http://opensource.apple.com/tarballs/CF/ 下载到整个 CoreFoundation 的源码来查看。

(Update: Swift 开源后,苹果又维护了一个跨平台的 CoreFoundation 版本:https://github.com/apple/swift-corelibs-foundation/,这个版本的源码可能和现有 iOS 系统中的实现略不一样,但更容易编译,而且已经适配了 Linux/Windows。)

RunLoop 与线程的关系
首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_thread_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。

苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 这两个函数内部的逻辑大概是下面这样:

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;

/// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);

if (!loopsDic) {
    // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
    loopsDic = CFDictionaryCreateMutable();
    CFRunLoopRef mainLoop = _CFRunLoopCreate();
    CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}

/// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));

if (!loop) {
    /// 取不到时,创建一个
    loop = _CFRunLoopCreate();
    CFDictionarySetValue(loopsDic, thread, loop);
    /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
    _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}

    OSSpinLockUnLock(&loopsLock);
    return loop;
}

CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}

CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

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

http://blog.ibireme.com/2015/05/18/runloop/

runloop是iOS系统对事件接受和分发机制的一个实现,是线程的基本架构部分。一个runloop就是一个事件处理循环,用来不停的调配工作以及处理输入事件。 使用runloop的目的是使你的线程在有工作的时候工作,没有的时候休眠,以达到节省cpu的目的。runloop的管理并不完全是自动,当我们创建一个子线程时,我们必须在适当的时候启动Runloop并正确响应事件。 子线程不需要显式的创建RunLoop,每个线程,包括程序的主线程都有与之对应的RunLoop对象,但是自己创建的线程需要手动运行RunLoop的运行方法。不过程序启动时,主线程会自动创建并运行RunLoop。

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop.

什么是Runloop

Runloop,顾名思义就是运行的循环。简单理解就是多线程机制中的基础,它能够接收外部事件的输入,并且在有事件的时候保持运行,在没有事件的时候进入休眠。并且它对于线程的消息处理机制进行了很好的封装。Runloop的作用就是要减少cpu做无谓的空转,cpu可在空闲的时候休眠,以节约电量。

对于线程来说,每一个线程都有一个runloop对象,是否能向某个线程的runloop发送事件取决于你是否启动了这个runloop,系统会默认在你的程序启动的时候运行主线程上的runloop,但是你自定义创建出来的线程可以不需要运行runloop,一些第三方框架,例如AFNetworking,就有在自己的线程上维护一个runloop对象。

在 Core Foundation 里面关于 RunLoop 有5个类:

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

他们的关系可以从NSRunloop对象的结构定义中得出。首先,runloop对象在Cocoa和Core Foundation中都有实现,但是他们做了很好的桥接,你可以直接调用

CFRunLoopRef runLoopRef = currentThreadRunLoop.getCFRunLoop;

来获取一个CoreFoundation中的runloop对象。然后,当你在查看NSRunloop的结构的时候,你应该能看到:

<CFRunLoop 0x7fd360f5af30 [0x1090a1180]>{wakeup port = 0x4507, stopped = false, ignoreWakeUps = true, 
current mode = (none),
common modes = <CFBasicHash 0x7fd360f5a470 [0x1090a1180]>{type = mutable set, count = 1,
entries =>
2 : <CFString 0x10907d080 [0x1090a1180]>{contents = "kCFRunLoopDefaultMode"}},
common mode items = (null),
modes = <CFBasicHash 0x7fd360f5b2b0 [0x1090a1180]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopMode 0x7fd360f5aff0 [0x1090a1180]>{name = kCFRunLoopDefaultMode, port set = 0x4703, timer port = 0x4803, 
sources0 = (null),
sources1 = (null),
observers = <CFArray 0x7fd360f5b1a0 [0x1090a1180]>{type = mutable-small, count = 1, values = (
0 : <CFRunLoopObserver 0x7fd360f5c7f0 [0x1090a1180]>{valid = Yes, activities = 0xfffffff, repeats = Yes, order = 0, callout = currentRunLoopObserver (0x10855b340), context = <CFRunLoopObserver context 0x7fd361213d70>}
)},
timers = <CFArray 0x7fd360e020d0 [0x1090a1180]>{type = mutable-small, count = 1, values = (
0 : <CFRunLoopTimer 0x7fd360e01f90 [0x1090a1180]>{valid = Yes, firing = No, interval = 1, tolerance = 0, next fire date = 463742311 (-2.53606331 @ 23607719248079), callout = (NSTimer) [SCCustomThread handleTimerTask] (0x1086416f1 / 0x10855b560) (/Users/useruser/Library/Developer/CoreSimulator/Devices/424D3C6E-8DC0-418B-A2EC-8EDF89507348/data/Containers/Bundle/Application/4D07AF38-9BFC-4617-BAE0-4CB0D7966CC8/runloopTest.app/runloopTest), context = <CFRunLoopTimer context 0x7fd360e01f70>}
)},
currently 463742313 (23610255156065) / soft deadline in: 1.84467441e+10 sec (@ 23607719248079) / hard deadline in: 1.84467441e+10 sec (@ 23607719248079)
},}}

可以看到一个runloop对象包含各种Mode——currentMode,common mode,modes等等,这里的示例我只指定了一个defaultMode。每个mode对应了source,observers和timers。

也许你会注意到 source 包括了source0和source1两个版本。

Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
CFRunLoopObserver类型的对象也可以称之为观察者。每个观察者都包含了一个回调,当runloop的状态发生变化时,你可以通过回调来知道当前的状态。

在你的程序中,runloop的过程实际上是一个无限循环的循环体,这个循环体是由你的程序来运行的。主线程的runloop由于系统已经实现并且没有它程序就不能运行,因此不需要我们手动去运行这个runloop。然而如果我们需要在自定义的线程中使用到runloop,我们则需要用一个do…while循环来驱动它。而runloop对象负责不断地在循环体中运行传进来的事件,然后将事件发给相应的响应。

如果你打开你的程序的main.m,你就会发现其实主线程的runloop就是在main函数中进行的,并且系统已经为你生成好了autoreleasepool,因此你也无需操心主线程上的内存释放到底是在什么时候执行了:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

根据响应源的不同,runloop也被分成了许多种不同的模式,这就是被Cocoa和Core Foundation都封装了的runloopMode。主要是这么几种:

NSDefaultRunLoopMode: 大多数工作中默认的运行方式。
NSConnectionReplyMode: 使用这个Mode去监听NSConnection对象的状态。
NSModalPanelRunLoopMode: 使用这个Mode在Model Panel情况下去区分事件(OS X开发中会遇到)。
NSEventTrackingRunLoopMode: 使用这个Mode去跟踪来自用户交互的事件(比如UITableView上下滑动)。
NSRunLoopCommonModes: 这是一个伪模式,其为一组run loop mode的集合。如果将Input source加入此模式,意味着关联Input source到Common Modes中包含的所有模式下。在iOS系统中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、NSEventTrackingRunLoopMode.可使用CFRunLoopAddCommonMode方法向Common Modes中添加自定义mode。

在文首的情况中,我们可以根据苹果官方文档的定义知道,当你在滑动页面的时候,主线程的runloop自动进入了NSEventTrackingRunLoopMode,而你的timer只是运行在DefaultMode下,所以不能响应。那么最简单的办法就是将你的timer添加在其他的mode下,像这样即可:

[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

需要注意的是CommonModes其实并不是一种Mode,而是一个集合。因此runloop并不能在CommonModes下运行,相反,你可以将需要输入的事件源添加为这个mode,这样无论runloop运行在哪个mode下都可以响应这个输入事件,否则这个事件将不会得到响应。

Input Source
输入源包括三种,端口,自定义输入源和performSelector的消息。根据上面的图我们可以看出,在runloop接收到消息并执行了指定方法的时候,它会执行runUntilDate:这个方法来退出当前循环。

端口源是基于Mach port的,其他进程或线程可以通过端口来发送消息。这里的知识点需要深入到Mach,就已经比较晦涩难懂了……这里你只需要知道你可以用Cocoa封装的NSPort对象来进行线程之间的通信,而这种通信方式所产生的事件就是通过端口源来传入runloop的。关于Mach port的更深层介绍可以看这篇。http://segmentfault.com/a/1190000002400329

自定义输入源。Core Foundation提供了CFRunLoopSourceRef类型的相关函数,可以用来创建自定义输入源。

performSelector输入源:

//在主线程的Run Loop下执行指定的 @selector 方法
  performSelectorOnMainThread:withObject:waitUntilDone:
  performSelectorOnMainThread:withObject:waitUntilDone:modes:

//在当前线程的Run Loop下执行指定的 @selector 方法
  performSelector:onThread:withObject:waitUntilDone:
  performSelector:onThread:withObject:waitUntilDone:modes:

//在当前线程的Run Loop下延迟加载指定的 @selector 方法
  performSelector:withObject:afterDelay:
  performSelector:withObject:afterDelay:inModes:

//取消当前线程的调用
  cancelPreviousPerformRequestsWithTarget:
  cancelPreviousPerformRequestsWithTarget:selector:object:

runloop生命周期
每一次runloop其实都是一次循环,runloop会在循环中执行runUntilDate: 或者runMode: beforeDate: 来开始每一个循环。而每一个循环又分为下面几个阶段,也就是runloop的生命周期:

kCFRunLoopEntry 进入循环
kCFRunLoopBeforeTimers 先接收timer的事件
kCFRunLoopBeforeSources 接收来自input source的事件
kCFRunLoopBeforeWaiting 如果没有事件,则准备进入休眠模式,在这里,如果没有事件传入,runloop会运行直到循环中给定的日期,如果你给的是distantFuture,那么这个runloop会无限等待下去
kCFRunLoopAfterWaiting 从休眠中醒来,直接回到kCFRunLoopBeforeTimers状态
kCFRunLoopExit 退出循环

这些状态也是一个枚举类型,系统是这么定义的,你可以使用observer来观测到这些状态:

/* Run Loop Observer Activities */
   typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
      kCFRunLoopEntry = (1UL << 0),
      kCFRunLoopBeforeTimers = (1UL << 1),
      kCFRunLoopBeforeSources = (1UL << 2),
      kCFRunLoopBeforeWaiting = (1UL << 5),
      kCFRunLoopAfterWaiting = (1UL << 6),
      kCFRunLoopExit = (1UL << 7),
      kCFRunLoopAllActivities = 0x0FFFFFFFU
   };

我们下面做一个测试,在demo中我们定义了一个新的线程类,这样我们可以自己启动和维护它的runloop对象。

- (void)main
{
    @autoreleasepool {
        NSLog(@"Thread Enter");
        [[NSThread currentThread] setName:@"This is a test thread"];
        NSRunLoop *currentThreadRunLoop = [NSRunLoop currentRunLoop];
        // 或者
        // CFRunLoopRef currentThreadRunLoop = CFRunLoopGetCurrent();
    
        CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &currentRunLoopObserver, &context);
    
        if (observer) {
            CFRunLoopRef runLoopRef = currentThreadRunLoop.getCFRunLoop;
            CFRunLoopAddObserver(runLoopRef, observer, kCFRunLoopDefaultMode);
        }
    
        // 创建一个Timer,重复调用来驱动Run Loop
        //[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(handleTimerTask) userInfo:nil repeats:YES];
        do {
            [currentThreadRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:3]];
        } while (1);
    }
}

输入源或者timer对于runloop来说是必要条件,如果没有添加任何输入源,则runloop根本不会启动,所以上面的代码中添加timer的操作,实际上是添加了一个默认的事件输入源,能让runloop保持运行。但是实际上,当你创建好一个runloop对象后,任何输入的事件都可以触发runloop的启动。

例如下面的:

[self performSelector:@selector(selectorTest) onThread:self.runLoopThread withObject:nil waitUntilDone:NO];

记住,如果你需要自己来启动和维护runloop的话,核心就在于一个do…while循环,你可以为runloop的跳出设置一个条件,也可以让runloop无限进行下去。在runloop没有接收到事件进入休眠状态之后,如果调用performSelector,runloop的状态变化如下:

Current thread Run Loop activity: kCFRunLoopAfterWaiting
Current thread Run Loop activity: kCFRunLoopBeforeTimers
Current thread Run Loop activity: kCFRunLoopBeforeSources
fuck
fuck_1
Current thread Run Loop activity: kCFRunLoopExit
Current thread Run Loop activity: kCFRunLoopEntry
Current thread Run Loop activity: kCFRunLoopBeforeTimers
Current thread Run Loop activity: kCFRunLoopBeforeSources
Current thread Run Loop activity: kCFRunLoopExit
Current thread Run Loop activity: kCFRunLoopEntry
Current thread Run Loop activity: kCFRunLoopBeforeTimers
Current thread Run Loop activity: kCFRunLoopBeforeSources
Current thread Run Loop activity: kCFRunLoopBeforeWaiting

在这里我连续调用了两次performSelector,可以看到runloop也经历了两个循环,而如果只调用一次的话,不会有多出来的那次runloop(你可以自己尝试一下),这是否说明每一次performSelector执行完毕之后都会立即结束当前runloop开始新的,苹果的官方文档里有一句话:

The run loop processes all queued perform selector calls each time through the loop, rather than processing one during each loop iteration

应该意思是并不是像上面看到的结果那样每一次循环执行一次,而是有一个待执行的操作队列。如果我同时执行四次performSelector,像这样:

[self performSelector:@selector(selectorTest) onThread:self.runLoopThread withObject:nil waitUntilDone:NO];
[self performSelector:@selector(selectorTest_1) onThread:self.runLoopThread withObject:nil waitUntilDone:NO];
[self performSelector:@selector(selectorTest_2) onThread:self.runLoopThread withObject:nil waitUntilDone:NO];
[self performSelector:@selector(selectorTest_2) onThread:self.runLoopThread withObject:nil waitUntilDone:NO];

实际上得到的结果和上面是一样的,然而当我将他们的waitUntilDone参数都设置为YES之后,我们可以看到不一样的地方:

Thread Enter
Current thread Run Loop activity: kCFRunLoopEntry
Current thread Run Loop activity: kCFRunLoopBeforeTimers
Current thread Run Loop activity: kCFRunLoopBeforeSources
fuck
Current thread Run Loop activity: kCFRunLoopExit
Current thread Run Loop activity: kCFRunLoopEntry
Current thread Run Loop activity: kCFRunLoopBeforeTimers
Current thread Run Loop activity: kCFRunLoopBeforeSources
fuck_1
Current thread Run Loop activity: kCFRunLoopExit
Current thread Run Loop activity: kCFRunLoopEntry
Current thread Run Loop activity: kCFRunLoopBeforeTimers
Current thread Run Loop activity: kCFRunLoopBeforeSources
fuck_2
Current thread Run Loop activity: kCFRunLoopExit
Current thread Run Loop activity: kCFRunLoopEntry
Current thread Run Loop activity: kCFRunLoopBeforeTimers
Current thread Run Loop activity: kCFRunLoopBeforeSources
fuck_2
Current thread Run Loop activity: kCFRunLoopExit
Current thread Run Loop activity: kCFRunLoopEntry
Current thread Run Loop activity: kCFRunLoopBeforeTimers
Current thread Run Loop activity: kCFRunLoopBeforeSources
Current thread Run Loop activity: kCFRunLoopBeforeWaiting

你可以看到每一个performSelector操作都单独执行了一个runloop,从苹果的文档中我们可以找到这个方法的定义:

performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
Performs the specified selector on any thread for which you have an NSThread object. These methods give you the option of blocking the current thread until the selector is performed.

也就是说,waitUntilDone意味着这个操作是否会在当前线程阻塞其他的输入源,如果等于True,则每一次runloop循环只会处理这一个selector的调用,如果为False,则队列中后面等待着的selector调用都会在同一次runloop循环中执行。至于上文的执行了两个runloop循环的现象,我猜测应该是当runloop从休眠模式被唤醒的时候,当前循环执行完唤醒的操作后就会立即结束,释放掉之前可能累积下来的内存,然后开始新的循环,将队列中的其他输入逐个放进runloop循环中执行。

来自http://sergiochan.xyz/2015/10/22/runloop初窥/

NSRunLoop的原理

以下内容来自RyanJIN http://www.jianshu.com/p/ebb3e42049fd的简书。
关于NSRunLoop推荐看一下来自百度工程师孙源的分享视频:http://v.youku.com/v_show/id_XODgxODkzODI0.html
RunLoop就是跑圈, 保证程序一直在执行. App运行起来之后, 即使你什么都不做, 放在那儿它也不会退出, 而是一直在"跑圈", 这就是RunLoop干的事. 主线程会自动创建一个RunLoop来保证程序一直运行. 但子线程默认不创建NSRunLoop, 所以子线程的任务一旦返回, 线程就over了.

上面的并发operation当start函数返回后子线程就退出了, 当NSURLConnection的delegate回调时, 线程已经木有了, 所以你也就收不到回调了. 为了保证子线程持续live(等待connection回调), 你需要在子线程中加入RunLoop, 来保证它不会被kill掉.

RunLoop在某一时刻只能在一种模式下运行, 更换模式时需要暂停当前的Loop, 然后重启新的Loop. RunLoop主要有下面几个模式:

NSDefalutRunLoopMode : 默认Mode, 通常主线程在这个模式下运行
UITrackingRunLoopMode : 滑动ScrollView是会切换到这个模式
NSRunLoopCommonModes: 包括上面两个模式

这边需要特别注意的是, 在滑动ScrollView的情况下, 系统会自动把RunLoop模式切换成UITrackingRunLoopMode来保证ScrollView的流畅性.

[NSTimer scheduledTimerWithTimeInterval:1.f
                             target:self
                           selector:@selector(timerAction:)   
                           userInfo:nil
                            reports:YES];

当你在滑动ScrollView的时候上面的timer会失效, 原因是Timer是默认加在NSDefalutRunLoopMode上的, 而滑动ScrollView后系统把RunLoop切换为UITrackingRunLoopMode, 所以timer就不会执行了. 解决方法是把该Timer加到NSRunLoopCommonModes下, 这样即使滑动ScrollView也不会影响timer了.

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
另外还有一个trick是当tableview的cell从网络异步加载图片, 加载完成后在主线程刷新显示图片, 这时滑动tableview会造成卡顿. 通常的思路是tableview滑动的时候延迟加载图片, 等停止滑动时再显示图片. 这里我们可以通过RunLoop来实现.

[self.cellImageView performSelector:@sector(setImage:)
                     withObject:downloadedImage
                     afterDelay:0
                        inModes:@[NSDefaultRunLoopMode]];

当NSRunLoop为NSDefaultRunLoopMode的时候tableview肯定停止滑动了, why? 因为如果还在滑动中, RunLoop的mode应该是UITrackingRunLoopMode.

呼叫NSURLConnection的异步回调
现在解决方案已经很清晰了, 就是利用RunLoop来监督线程, 让它一直等待delegate的回调. 上面已经说到Main Thread是默认创建了一个RunLoop的, 所以我们的Option 1是让start函数在主线程运行(即使[operation start]是在子线程调用的).

- (void)start 
{
     if (![NSThread isMainThread]) {
          [self performSelectorOnMainThread:@selector(start)
                           withObject:nil
                        waitUntilDone:NO];
          return;
    }
// set up NSURLConnection...
}

或者这样:

- (void)start
{
       [[NSOperationQueue mainQueue] addOperationWithBlock:^{
       self.connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
       }];
}

这样我们可以简单直接的使用main run loop, 因为数据delivery是非常快滴. 然后我们就可以将处理incoming data的操作放到子线程去...

Option 2是让operation的start函数在子线程运行, 但是我们为它创建一个RunLoop. 然后把URL connection schedule到上面去. 我们先来瞅瞅AFNetworking是怎么做滴:

+ (void)networkRequestThreadEntryPoint:(id)__unused object 
{
     @autoreleasepool {
          [[NSThread currentThread] setName:@"AFNetworking"];
          NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
          [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
          [runLoop run];
     }
}

+ (NSThread *)networkRequestThread 
{
     static NSThread *_networkRequestThread = nil;
     static dispatch_once_t oncePredicate;
     dispatch_once(&oncePredicate, ^{
     _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
     [_networkRequestThread start];
     });
     return _networkRequestThread;
 }

 - (void)start 
 {
      [self.lock lock];
      if ([self isCancelled]) {
           [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
      } else if ([self isReady]) {
           self.state = AFOperationExecutingState;
           [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
      }
      [self.lock unlock];
 }

AFNetworking创建了一个新的子线程(在子线程中调用NSRunLoop *runloop = [NSRunLoop currentRunLoop]; 获取RunLoop对象的时候, 就会创建RunLoop), 然后把它加到RunLoop里面来保证它一直运行.

这边我们可以简单的判断下当前start()的线程是子线程还是主线程, 如果是子线程则调用[NSRunLoop currentRunLoop]创新RunLoop, 否则就直接调用[NSRunLoop mainRunLoop], 当然在主线程下就没必要调用[runLoop run]了, 因为它本来就是一直run的.
P.S. 我们还可以使用CFRunLoop来启动和停止RunLoop, 像下面这样:

[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
                       forMode:NSRunLoopCommonModes];
CFRunLoopRun();

等到该Operation结束的时候, 一定要记得调用CFRunLoopStop()停止当前线程的RunLoop, 让当前线程在operation finished之后可以退出.
多线程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容