在开始聊RunLoop之前,我们先来了解一下程序的执行原理。一般来说,程序是在线程中执行,一个线程一次只能执行一个任务(关于GCD,可看上篇文章介绍),执行完成后线程就会退出。类似这样:

1 int main(int argc, char * argv[]) {
2     @autoreleasepool {
3         NSLog(@"我开始干活了");
4         
5         return 0;
6     }
7 }

在我们的App中,我们需要的是这样一个机制:线程能随时处理事件但不退出。这种机制叫做Event Loop,例如Windows系统下的消息循环,OSX/iOS里的Run Loop。

还是先看看我们实际App中的Main函数:

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

我们稍微改造一下,来细致的分析:

1 int main(int argc, char * argv[]) {
2     @autoreleasepool {
3         int iRet = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
4         NSLog(@"%d", iRet);  //这个不会打印
5         return iRet;
6     }
7 }

为什么那个NSLog没有打印?我们看一下运行堆栈信息,从而分析一下UIApplicationMain都干了什么事:

聊聊Runloop

可以看到,有一系列CFRunLoop的相关信息,这些后面分析原理的时候来细谈。我们根据上面的堆栈信息,来大致对RunLoop有个初步总结:

  • UIApplicationMain函数中启动了一个RunLoop。这个RunLoop是和主线程相关联的,它是主线程的一部分(其实每一个线程都有一个RunLoop,但其他线程的RunLoop默认没有开启。如果想让某个线程一直活着,那么需要开启RunLoop);
  • RunLoop管理了需要处理的事件和消息。并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回;
  • RunLoop保持程序的持续运行(主线程没有退出);
  • RunLoop能够处理App中的各种事件(如触摸事件、定时器事件、Selector事件);
  • 使用RunLoop机制,能够有效的节省CPU资源,提高程序性能(有事做就做事,没事做就休息)。

2.RunLoop原理分析

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

2.1RunLoop和线程

苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。我们可以看一下系统实现这两个方法的源码:

//全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
//访问 loopsDic 时的锁
static CFLock_t loopsLock = CFLockInit;

// 获取一个 pthread 对应的 RunLoop。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
       CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
       CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
       CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
       if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
           CFRelease(dict);
       }
       CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // 直接从 Dictionary 里获取。
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    //  取不到时,创建一个
    if (!loop) {
       CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
       loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
       if (!loop) {
           CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
       }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
       CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        // 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

从上面的代码,我们总结一下线程和RunLoop的关系:

  • 每条线程都有唯一的一个与之对应的RunLoop对象,它们的关系保存在一个全局的 Dictionary 里;
  • 主线程的RunLoop已经自动创建和启动了,子线程的RunLoop需要手动创建和启动,如果不主动获取,那它一直都不会有;
  • RunLoop是线程的一部分,它的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时;
  • 只能在一个线程的内部获取其 RunLoop(主线程除外)。

 2.2RunLoop相关类

在 CoreFoundation 里面关于 RunLoop 有5个类:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

 它们的关系如下图所示:

聊聊Runloop

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

2.2.1CFRunLoopRef

先看看它的源码:

 1 头文件CFRunLoop.h
 2 typedef struct __CFRunLoop * CFRunLoopRef;
 3 
 4 CFRunLoop.c
 5 struct __CFRunLoop {
 6     CFRuntimeBase _base;
 7     pthread_mutex_t _lock;            /* locked for accessing mode list */
 8     __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp 
 9     Boolean _unused;
10     volatile _per_run_data *_perRunData;              // reset for runs of the run loop
11     pthread_t _pthread;
12     uint32_t _winthread;
13     CFMutableSetRef _commonModes;
14     CFMutableSetRef _commonModeItems;
15     CFRunLoopModeRef _currentMode;
16     CFMutableSetRef _modes;
17     struct _block_item *_blocks_head;
18     struct _block_item *_blocks_tail;
19     CFAbsoluteTime _runTime;
20     CFAbsoluteTime _sleepTime;
21     CFTypeRef _counterpart;
22 };

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

咱们通过一个示例来说明:

1     self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(startTimer) userInfo:nil repeats:YES];
2     [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

对上面的示例做个解释:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为"Common"属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪用户交互事件时的状态。当创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

有时我们需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 "commonModeItems" 中。"commonModeItems" 被 RunLoop 自动更新到所有具有"Common"属性的 Mode 里去。

在Core Foundation框架中提供了两个方法来获取CFRunLoop对象(CFRunLoop.h文件):

1 CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void);
2 CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void);

在Foundation框架中提供了相应的两个方法来获取RunLoop:

1 [NSRunLoop currentRunLoop];  //当前线程的RunLoop
2 [NSRunLoop mainRunLoop];  //主线程的RunLoop

 下面打印了一个RunLoop运行时的Log:

<CFRunLoop 0x7fb4a9422dc0 [0x1046fceb0]>{wakeup port = 0xf03, stopped = false, ignoreWakeUps = false, 

current mode = UIInitializationRunLoopMode,

common modes = <CFBasicHash 0x7fb4a9614860 [0x1046fceb0]>{type = mutable set, count = 2,
entries =>
    0 : <CFString 0x105fc8370 [0x1046fceb0]>{contents = "UITrackingRunLoopMode"}
    2 : <CFString 0x1046d8f30 [0x1046fceb0]>{contents = "kCFRunLoopDefaultMode"}
},

common mode items = <CFBasicHash 0x7fb4a950a4d0 [0x1046fceb0]>{type = mutable set, count = 16,
entries =>
    0 : <CFRunLoopSource 0x7fb4a9808a10 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x109ab46e2)}}
    1 : <CFRunLoopSource 0x7fb4a9634160 [0x1046fceb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = FBSSerialQueueRunLoopSourceHandler (0x10bc34033)}}
    2 : <CFRunLoopObserver 0x7fb4a942b830 [0x1046fceb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1054326a3), context = <CFArray 0x7fb4a942b800 [0x1046fceb0]>{type = mutable-small, count = 0, values = ()}}
    4 : <CFRunLoopSource 0x7fb4a942b5e0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 11527, subsystem = 0x105f95d20, context = 0x0}}
    6 : <CFRunLoopSource 0x7fb4a950a9c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 1, context = <CFMachPort 0x7fb4a96263b0 [0x1046fceb0]>{valid = Yes, port = 1803, source = 0x7fb4a950a9c0, callout = __IOMIGMachPortPortCallback (0x10a46e572), context = <CFMachPort context 0x7fb4a9626340>}}
    9 : <CFRunLoopSource 0x7fb4a9823240 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 15879, subsystem = 0x105fb43b0, context = 0x7fb4a9816980}}
    10 : <CFRunLoopSource 0x7fb4a96267c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2103, callout = PurpleEventCallback (0x109ab6e6b)}}
    11 : <CFRunLoopSource 0x7fb4a942ba10 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x7fb4a9626b30, callout = _UIApplicationHandleEventQueue (0x105432a2a)}}
    12 : <CFRunLoopSource 0x7fb4a950a3f0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a9808950 [0x1046fceb0]>{valid = Yes, port = 1a03, source = 0x7fb4a950a3f0, callout = __IOHIDEventSystemClientQueueCallback (0x10a4665ef), context = <CFMachPort context 0x7fb4a98086b0>}}
    13 : <CFRunLoopObserver 0x7fb4a98172d0 [0x1046fceb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1057e3965), context = <CFRunLoopObserver context 0x0>}
    14 : <CFRunLoopObserver 0x7fb4a942b6a0 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _afterCACommitHandler (0x105459751), context = <CFRunLoopObserver context 0x7fb4a9626b30>}
    15 : <CFRunLoopSource 0x7fb4a950a890 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a99002e0 [0x1046fceb0]>{valid = Yes, port = 1b03, source = 0x7fb4a950a890, callout = __IOHIDEventSystemClientAvailabilityCallback (0x10a466850), context = <CFMachPort context 0x7fb4a98086b0>}}
    18 : <CFRunLoopObserver 0x7fb4a9816180 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x105226a94), context = <CFRunLoopObserver context 0x0>}
    19 : <CFRunLoopObserver 0x7fb4a962e270 [0x1046fceb0]>{valid = Yes, activities = 0xfffffff, repeats = No, order = 0, callout = orderOutContextObserverCallout (0x10544c1d9), context = <CFRunLoopObserver context 0x0>}
    20 : <CFRunLoopSource 0x7fb4a980d1c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a980cf30 [0x1046fceb0]>{valid = Yes, port = 2307, source = 0x7fb4a980d1c0, callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_ (0x1052e7328), context = <CFMachPort context 0x0>}}
    22 : <CFRunLoopObserver 0x7fb4a942b8d0 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1054326a3), context = <CFArray 0x7fb4a942b800 [0x1046fceb0]>{type = mutable-small, count = 0, values = ()}}
},

modes = <CFBasicHash 0x7fb4a9613d70 [0x1046fceb0]>{type = mutable set, count = 5,
entries =>
    2 : <CFRunLoopMode 0x7fb4a950a5d0 [0x1046fceb0]>{name = UITrackingRunLoopMode, port set = 0x1d03, timer port = 0x1e03, 
        sources0 = <CFBasicHash 0x7fb4a950a530 [0x1046fceb0]>{type = mutable set, count = 3,
        entries =>
            0 : <CFRunLoopSource 0x7fb4a9808a10 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x109ab46e2)}}
            1 : <CFRunLoopSource 0x7fb4a9634160 [0x1046fceb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = FBSSerialQueueRunLoopSourceHandler (0x10bc34033)}}
            2 : <CFRunLoopSource 0x7fb4a942ba10 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x7fb4a9626b30, callout = _UIApplicationHandleEventQueue (0x105432a2a)}}
        },
        sources1 = <CFBasicHash 0x7fb4a950a570 [0x1046fceb0]>{type = mutable set, count = 7,
        entries =>
            1 : <CFRunLoopSource 0x7fb4a96267c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2103, callout = PurpleEventCallback (0x109ab6e6b)}}
            3 : <CFRunLoopSource 0x7fb4a950a3f0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a9808950 [0x1046fceb0]>{valid = Yes, port = 1a03, source = 0x7fb4a950a3f0, callout = __IOHIDEventSystemClientQueueCallback (0x10a4665ef), context = <CFMachPort context 0x7fb4a98086b0>}}
            6 : <CFRunLoopSource 0x7fb4a9823240 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 15879, subsystem = 0x105fb43b0, context = 0x7fb4a9816980}}
            9 : <CFRunLoopSource 0x7fb4a942b5e0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 11527, subsystem = 0x105f95d20, context = 0x0}}
            10 : <CFRunLoopSource 0x7fb4a980d1c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a980cf30 [0x1046fceb0]>{valid = Yes, port = 2307, source = 0x7fb4a980d1c0, callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_ (0x1052e7328), context = <CFMachPort context 0x0>}}
            11 : <CFRunLoopSource 0x7fb4a950a9c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 1, context = <CFMachPort 0x7fb4a96263b0 [0x1046fceb0]>{valid = Yes, port = 1803, source = 0x7fb4a950a9c0, callout = __IOMIGMachPortPortCallback (0x10a46e572), context = <CFMachPort context 0x7fb4a9626340>}}
            12 : <CFRunLoopSource 0x7fb4a950a890 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a99002e0 [0x1046fceb0]>{valid = Yes, port = 1b03, source = 0x7fb4a950a890, callout = __IOHIDEventSystemClientAvailabilityCallback (0x10a466850), context = <CFMachPort context 0x7fb4a98086b0>}}
        },
        observers = <CFArray 0x7fb4a942b740 [0x1046fceb0]>{type = mutable-small, count = 6, values = (
            0 : <CFRunLoopObserver 0x7fb4a942b830 [0x1046fceb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1054326a3), context = <CFArray 0x7fb4a942b800 [0x1046fceb0]>{type = mutable-small, count = 0, values = ()}}
            1 : <CFRunLoopObserver 0x7fb4a98172d0 [0x1046fceb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1057e3965), context = <CFRunLoopObserver context 0x0>}
            2 : <CFRunLoopObserver 0x7fb4a962e270 [0x1046fceb0]>{valid = Yes, activities = 0xfffffff, repeats = No, order = 0, callout = orderOutContextObserverCallout (0x10544c1d9), context = <CFRunLoopObserver context 0x0>}
            3 : <CFRunLoopObserver 0x7fb4a942b6a0 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _afterCACommitHandler (0x105459751), context = <CFRunLoopObserver context 0x7fb4a9626b30>}
            4 : <CFRunLoopObserver 0x7fb4a9816180 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x105226a94), context = <CFRunLoopObserver context 0x0>}
            5 : <CFRunLoopObserver 0x7fb4a942b8d0 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1054326a3), context = <CFArray 0x7fb4a942b800 [0x1046fceb0]>{type = mutable-small, count = 0, values = ()}}
        )},
        timers = (null),
        currently 511066692 (6525220218952) / soft deadline in: 1.84467375e+10 sec (@ -1) / hard deadline in: 1.84467375e+10 sec (@ -1)
    },
    3 : <CFRunLoopMode 0x7fb4a9808bf0 [0x1046fceb0]>{name = GSEventReceiveRunLoopMode, port set = 0x1f03, timer port = 0x2003, 
        sources0 = <CFBasicHash 0x7fb4a98083c0 [0x1046fceb0]>{type = mutable set, count = 1,
        entries =>
            0 : <CFRunLoopSource 0x7fb4a9808a10 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x109ab46e2)}}
        },
        sources1 = <CFBasicHash 0x7fb4a9808ca0 [0x1046fceb0]>{type = mutable set, count = 1,
        entries =>
            0 : <CFRunLoopSource 0x7fb4a9626970 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2103, callout = PurpleEventCallback (0x109ab6e6b)}}
        },
        observers = (null),
        timers = (null),
        currently 511066692 (6525220881743) / soft deadline in: 1.84467375e+10 sec (@ -1) / hard deadline in: 1.84467375e+10 sec (@ -1)
    },
    4 : <CFRunLoopMode 0x7fb4a9613aa0 [0x1046fceb0]>{name = kCFRunLoopDefaultMode, port set = 0x1003, timer port = 0x1103, 
        sources0 = <CFBasicHash 0x7fb4a950a770 [0x1046fceb0]>{type = mutable set, count = 3,
        entries =>
            0 : <CFRunLoopSource 0x7fb4a9808a10 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x109ab46e2)}}
            1 : <CFRunLoopSource 0x7fb4a9634160 [0x1046fceb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = FBSSerialQueueRunLoopSourceHandler (0x10bc34033)}}
            2 : <CFRunLoopSource 0x7fb4a942ba10 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x7fb4a9626b30, callout = _UIApplicationHandleEventQueue (0x105432a2a)}}
        },
        sources1 = <CFBasicHash 0x7fb4a950a7b0 [0x1046fceb0]>{type = mutable set, count = 7,
        entries =>
            1 : <CFRunLoopSource 0x7fb4a96267c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2103, callout = PurpleEventCallback (0x109ab6e6b)}}
            3 : <CFRunLoopSource 0x7fb4a950a3f0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a9808950 [0x1046fceb0]>{valid = Yes, port = 1a03, source = 0x7fb4a950a3f0, callout = __IOHIDEventSystemClientQueueCallback (0x10a4665ef), context = <CFMachPort context 0x7fb4a98086b0>}}
            6 : <CFRunLoopSource 0x7fb4a9823240 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 15879, subsystem = 0x105fb43b0, context = 0x7fb4a9816980}}
            9 : <CFRunLoopSource 0x7fb4a942b5e0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 11527, subsystem = 0x105f95d20, context = 0x0}}
            10 : <CFRunLoopSource 0x7fb4a980d1c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a980cf30 [0x1046fceb0]>{valid = Yes, port = 2307, source = 0x7fb4a980d1c0, callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_ (0x1052e7328), context = <CFMachPort context 0x0>}}
            11 : <CFRunLoopSource 0x7fb4a950a9c0 [0x1046fceb0]>{signalled = No, valid = Yes, order = 1, context = <CFMachPort 0x7fb4a96263b0 [0x1046fceb0]>{valid = Yes, port = 1803, source = 0x7fb4a950a9c0, callout = __IOMIGMachPortPortCallback (0x10a46e572), context = <CFMachPort context 0x7fb4a9626340>}}
            12 : <CFRunLoopSource 0x7fb4a950a890 [0x1046fceb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x7fb4a99002e0 [0x1046fceb0]>{valid = Yes, port = 1b03, source = 0x7fb4a950a890, callout = __IOHIDEventSystemClientAvailabilityCallback (0x10a466850), context = <CFMachPort context 0x7fb4a98086b0>}}
        },
        observers = <CFArray 0x7fb4a942b7a0 [0x1046fceb0]>{type = mutable-small, count = 6, values = (
            0 : <CFRunLoopObserver 0x7fb4a942b830 [0x1046fceb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1054326a3), context = <CFArray 0x7fb4a942b800 [0x1046fceb0]>{type = mutable-small, count = 0, values = ()}}
            1 : <CFRunLoopObserver 0x7fb4a98172d0 [0x1046fceb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1057e3965), context = <CFRunLoopObserver context 0x0>}
            2 : <CFRunLoopObserver 0x7fb4a962e270 [0x1046fceb0]>{valid = Yes, activities = 0xfffffff, repeats = No, order = 0, callout = orderOutContextObserverCallout (0x10544c1d9), context = <CFRunLoopObserver context 0x0>}
            3 : <CFRunLoopObserver 0x7fb4a942b6a0 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _afterCACommitHandler (0x105459751), context = <CFRunLoopObserver context 0x7fb4a9626b30>}
            4 : <CFRunLoopObserver 0x7fb4a9816180 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x105226a94), context = <CFRunLoopObserver context 0x0>}
            5 : <CFRunLoopObserver 0x7fb4a942b8d0 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1054326a3), context = <CFArray 0x7fb4a942b800 [0x1046fceb0]>{type = mutable-small, count = 0, values = ()}}
        )},
        timers = <CFArray 0x7fb4a9809170 [0x1046fceb0]>{type = mutable-small, count = 1, values = (
            0 : <CFRunLoopTimer 0x7fb4a96349b0 [0x1046fceb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 511066694 (1.441432 @ 6526663103544), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x104889162 / 0x105749b63) (/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 8.2.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x7fb4a9629600>}
        )},
        currently 511066692 (6525220919500) / soft deadline in: 1.44218403 sec (@ 6526663103544) / hard deadline in: 1.442184 sec (@ 6526663103544)
    },
    5 : <CFRunLoopMode 0x7fb4a9634350 [0x1046fceb0]>{name = UIInitializationRunLoopMode, port set = 0x2703, timer port = 0x2803, 
        sources0 = <CFBasicHash 0x7fb4a9634400 [0x1046fceb0]>{type = mutable set, count = 1,
        entries =>
            0 : <CFRunLoopSource 0x7fb4a9634160 [0x1046fceb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = FBSSerialQueueRunLoopSourceHandler (0x10bc34033)}}
        },
        sources1 = <CFBasicHash 0x7fb4a9634440 [0x1046fceb0]>{type = mutable set, count = 0,
        entries =>
        },
        observers = <CFArray 0x7fb4a9816280 [0x1046fceb0]>{type = mutable-small, count = 1, values = (
            0 : <CFRunLoopObserver 0x7fb4a9816180 [0x1046fceb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x105226a94), context = <CFRunLoopObserver context 0x0>}
        )},
        timers = (null),
        currently 511066692 (6525221705051) / soft deadline in: 1.84467375e+10 sec (@ -1) / hard deadline in: 1.84467375e+10 sec (@ -1)
    },
    6 : <CFRunLoopMode 0x7fb4a9b00ab0 [0x1046fceb0]>{name = kCFRunLoopCommonModes, port set = 0x3403, timer port = 0x3503, 
        sources0 = (null),
        sources1 = (null),
        observers = (null),
        timers = (null),
        currently 511066692 (6525221827512) / soft deadline in: 1.84467375e+10 sec (@ -1) / hard deadline in: 1.84467375e+10 sec (@ -1)
    },
}
}

2.2.2CFRunLoopModeRef

还是从源码来分析结构:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

关于CFRunLoopModeRef,先有几个思维概念:

  • CFRunLoopModeRef代表RunLoop的运行模式,如果没有一个Mode,那么RunLoop就会直接退出;
  • 一个RunLoop包含若干个Mode,每个Mode又包含了若干个Source、Timer、Observer,这些Source、Timer、Observer可以理解为item;如果某个Mode没有一个item,那么RunLoop在该Mode下运行会直接退出;
  • 每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode;
  • 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入。

从上一小节的运行Log可以看到,启动App时,系统默认注册了5个Mode:

  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行;
  • UITrackingRunLoopMode:用户交互模式,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响;只要有用户交互事件,那么RunLoop就会切换到该模式。
  • kCFRunLoopCommonModes:占位模式。占有了上面两种模式,在这两种模式下都有效果。
  • GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到;
  • UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后就不再使用;

CFRunLoop对外暴露的管理 Mode 接口只有下面2个:

CF_EXPORT void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode);
CF_EXPORT SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);

Mode 暴露的管理 item 的接口有下面几个:

CF_EXPORT Boolean CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
CF_EXPORT void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
CF_EXPORT void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);

CF_EXPORT Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
CF_EXPORT void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);

CF_EXPORT Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CF_EXPORT void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

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

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

我们可以通过下面的方式,来获取当前运行的Mode和所有的Mode:

CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
//获取当前运行的Mode
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(currentRunloop);
NSLog(@"%@", mode);
//获取所有的Mode    
CFArrayRef allModes = CFRunLoopCopyAllModes(currentRunloop);
NSLog(@"%@", allModes);

2.2.3CFRunLoopSourceRef

先看源码:

//CFRunLoop.h文件
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

//version0
typedef struct {
    CFIndex    version;
    void *    info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef    (*copyDescription)(const void *info);
    Boolean    (*equal)(const void *info1, const void *info2);
    CFHashCode    (*hash)(const void *info);
    void    (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*perform)(void *info);
} CFRunLoopSourceContext;

//version1
typedef struct {
    CFIndex    version;
    void *    info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef    (*copyDescription)(const void *info);
    Boolean    (*equal)(const void *info1, const void *info2);
    CFHashCode    (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t    (*getPort)(void *info);
    void *    (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *    (*getPort)(void *info);
    void    (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

//CFRunLoop.c
struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;            /* immutable */
    CFMutableBagRef _runLoops;
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};

CFRunLoopSourceRef 是事件产生的地方。从源码可以看到,它定义了两个版本:Source0 和 Source1:

  • Source0:非基于Port的,只包含了一个回调(函数指针),它并不能主动触发事件,处理的是App内部的事件,App自己负责管理,如点击按钮、点击屏幕、触摸事件、自定义输入源、performSelector:onThread:等。使用时,需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  • Source1:包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息,Mach Port驱动,如CFMachPort、CFMessagePort。

Source0和Source1的区别,最主要的就是Source0需要手动标记为待处理,并且去唤醒Runloop。Source1不需要。

在这里对触摸事件做一个简单的说明,详细的可看这篇博客

在2.2.1小节的Log中,我们可以看到系统注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用系统注册的一个Source0,对应回调函数 _UIApplicationHandleEventQueue() 进行应用内部的分发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

_UIApplicationHandleEventQueue() 识别了一个手势时,首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。

系统注册了一个 Observer,其回调函数是回调函数是 _UIGestureRecognizerUpdateObserver(),用来监测 BeforeWaiting (Loop即将进入休眠) 事件,其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

下面代码演示了自定义Source的操作:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self addSource];
}

- (void)addSource {
    if (0 == self.index) {  //初始化Source
        CFRunLoopSourceContext context = {
            0,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            schedule,
            cancel,
            perform
        };
        source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
    }
    else if (1 == self.index) {  //添加源
        CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
    }
    else if (2 == self.index) {  //将source置为待处理并且唤醒Runloop
        CFRunLoopSourceSignal(source0);
        CFRunLoopWakeUp(CFRunLoopGetCurrent());
    }
    else if (3 == self.index) {  //移除源
        CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
        //移除源要做释放操作
        CFRelease(source0);
    }
    self.index++;
}

//添加源到Runloop时回调
//CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    NSLog(@"LeeGof %s", __func__);
}

//移除源的时候回调
//CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    NSLog(@"LeeGof %s", __func__);
}

//手动将source置为待处理并且唤醒Runloop的时候回调
//CFRunLoopSourceSignal(source0);
//CFRunLoopWakeUp(CFRunLoopGetCurrent());
void perform(void *info){
    NSLog(@"LeeGof %s", __func__);
}

关于Source1里说的port,这里我们先看一下官方文档的介绍:

Communication occurs between NSPort objects, which typically reside in different threads or tasks. The distributed objects system uses NSPort objects to send NSPortMessage objects back and forth. Implement interapplication communication using distributed objects whenever possible and use NSPort objects only when necessary.

To receive incoming messages, NSPort objects must be added to an NSRunLoop object as input sources. NSConnection objects automatically add their receive port when initialized.

When an NSPort object receives a port message, it forwards the message to its delegate in a handleMachMessage: or handlePortMessage: message. The delegate should implement only one of these methods to process the incoming message in whatever form desired. handleMachMessage: provides a message as a raw Mach message beginning with a msg_header_t structure. handlePortMessage: provides a message as an NSPortMessage object, which is an object-oriented wrapper for a Mach message. If a delegate has not been set, the NSPort object handles the message itself.

When you are finished using a port object, you must explicitly invalidate the port object prior to sending it a release message. Similarly, if your application uses garbage collection, you must invalidate the port object before removing any strong references to it. If you do not invalidate the port, the resulting port object may linger and create a memory leak. To invalidate the port object, invoke its invalidate method.

Foundation defines three concrete subclasses of NSPort. NSMachPort and NSMessagePort allow local (on the same machine) communication only. NSSocketPort allows for both local and remote communication, but may be more expensive than the others for the local case. When creating an NSPort object, using allocWithZone: or port, an NSMachPort object is created instead.
View Code

相关文章: