Runloop【整理版】

2018-08-13  本文已影响18人  小白猿

概念

  1. 字面意思:就是转圈圈,运行循环
    类似于do ... while
  2. 基本作用:
  1. 对比通过循环调用
  1. 官方资料

API

iOS中有两套API访问和使用Runloop

[NSRunLoop currentRunLoop]; // 获取当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获取主线程的RunLoop对象
CFRunLoopGetCurrent(); // 获取当前线程的RunLoop对象
CFRunLoopGetMain(); //  // 获取主线程的RunLoop对象

与线程的关系

具体创建

1.源码分析【CFRunLoop.c文件中 _CFRunLoopGet0方法】

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
/**
 获取(创建)RunLoop的方法
 @param t 线程
 */
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    // 判断是不是主线程
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    // 判断主线程对应的RunLoop是否存在,不存在的话,执行里面的代码
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        // 主线程对应的RunLoop是默认创建好的
        // 创建字典(以线程为key,以RunLoop为value)
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // 创建(获取)主线程对应的RunLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // 字典存储: 以线程为key,以RunLoop为value
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // 从字典中获取子线程对应的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    
    if (!loop) {
        // 如果子线程对应的RunLoop不存在,就创建子线程对应的RunLoop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        // 把当前子线程对应的RunLoop存储到字段中,以当前子线程为key
    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);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

2.代码创建过程

- (void)viewDidLoad {
    [super viewDidLoad];
    // Foundatain
    NSRunLoop *loop1 = [NSRunLoop mainRunLoop];
    NSRunLoop *loop2 = [NSRunLoop currentRunLoop];
    NSLog(@"\n loop1:%p ---\n loop2:%p ---- ",loop1,loop2);
    // Core Foundation
    NSLog(@"\n%p\n%p",CFRunLoopGetMain(),CFRunLoopGetCurrent());
    // 转换
    NSLog(@"\n---%p",loop1.getCFRunLoop);
    // 打印RunLoop对象
    NSLog(@"\n ********%@",loop1);
    
}

打印结果

2018-08-13 19:19:26.177558+0800 OC_Prac[25380:1654445] 
 loop1:0x6000000ae5e0 ---
 loop2:0x6000000ae5e0 ----
2018-08-13 19:19:26.177751+0800 OC_Prac[25380:1654445] 
0x6040001f6c00
0x6040001f6c00
2018-08-13 19:19:26.177888+0800 OC_Prac[25380:1654445] 
---0x6040001f6c00
2018-08-13 19:19:26.186345+0800 OC_Prac[25380:1654445] 
 ********<CFRunLoop 0x6040001f6c00 [0x103bcfc80]>{wakeup port = 0x1903, stopped = false, ignoreWakeUps = false, 
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x604000445f40 [0x103bcfc80]>{type = mutable set, count = 2,
entries =>
    0 : <CFString 0x104f3fe88 [0x103bcfc80]>{contents = "UITrackingRunLoopMode"}
    2 : <CFString 0x103ba5818 [0x103bcfc80]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x604000446360 [0x103bcfc80]>{type = mutable set, count = 13,
entries =>
    0 : <CFRunLoopSource 0x60400017d880 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x108b1a75a)}}
    1 : <CFRunLoopSource 0x60400017f140 [0x103bcfc80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040000acde0, callout = FBSSerialQueueRunLoopSourceHandler (0x10828582f)}}
    4 : <CFRunLoopObserver 0x600000320960 [0x103bcfc80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1043636b3), context = <CFRunLoopObserver context 0x6040000cbfa0>}
    10 : <CFRunLoopSource 0x60000017e9c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 15627, subsystem = 0x104ef6fe8, context = 0x0}}
    11 : <CFRunLoopObserver 0x600000320500 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x103dacda1), context = <CFRunLoopObserver context 0x7f9325f009d0>}
    12 : <CFRunLoopSource 0x60000017d7c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000340420, callout = __handleEventQueue (0x1046dabb2)}}
    13 : <CFRunLoopObserver 0x6000003203c0 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = <CFArray 0x60000024a980 [0x103bcfc80]>{type = mutable-small, count = 1, values = (
    0 : <0x7f9326005048>
)}}
    14 : <CFRunLoopObserver 0x600000320320 [0x103bcfc80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = <CFArray 0x60000024a980 [0x103bcfc80]>{type = mutable-small, count = 1, values = (
    0 : <0x7f9326005048>
)}}
    15 : <CFRunLoopObserver 0x600000320460 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x103dace1c), context = <CFRunLoopObserver context 0x7f9325f009d0>}
    16 : <CFRunLoopObserver 0x604000320820 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10994b4ce), context = <CFRunLoopObserver context 0x0>}
    20 : <CFRunLoopSource 0x60000017e540 [0x103bcfc80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600000249c60, callout = __handleHIDEventFetcherDrain (0x1046dabbe)}}
    21 : <CFRunLoopSource 0x60000017d100 [0x103bcfc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42755, subsystem = 0x104f11668, context = 0x600000036e00}}
    22 : <CFRunLoopSource 0x60400017d7c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x5303, callout = PurpleEventCallback (0x108b1cbf7)}}
}
,
modes = <CFBasicHash 0x604000445fd0 [0x103bcfc80]>{type = mutable set, count = 4,
entries =>
    2 : <CFRunLoopMode 0x604000197010 [0x103bcfc80]>{name = UITrackingRunLoopMode, port set = 0x1f03, queue = 0x6040003406e0, source = 0x6040001971b0 (not fired), timer port = 0x2a03, 
    sources0 = <CFBasicHash 0x6040004464b0 [0x103bcfc80]>{type = mutable set, count = 4,
entries =>
    0 : <CFRunLoopSource 0x60400017d880 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x108b1a75a)}}
    1 : <CFRunLoopSource 0x60000017d7c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000340420, callout = __handleEventQueue (0x1046dabb2)}}
    2 : <CFRunLoopSource 0x60400017f140 [0x103bcfc80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040000acde0, callout = FBSSerialQueueRunLoopSourceHandler (0x10828582f)}}
    4 : <CFRunLoopSource 0x60000017e540 [0x103bcfc80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600000249c60, callout = __handleHIDEventFetcherDrain (0x1046dabbe)}}
}
,
    sources1 = <CFBasicHash 0x6040004464e0 [0x103bcfc80]>{type = mutable set, count = 3,
entries =>
    0 : <CFRunLoopSource 0x60000017e9c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 15627, subsystem = 0x104ef6fe8, context = 0x0}}
    1 : <CFRunLoopSource 0x60000017d100 [0x103bcfc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42755, subsystem = 0x104f11668, context = 0x600000036e00}}
    2 : <CFRunLoopSource 0x60400017d7c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x5303, callout = PurpleEventCallback (0x108b1cbf7)}}
}
,
    observers = (
    "<CFRunLoopObserver 0x600000320320 [0x103bcfc80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = <CFArray 0x60000024a980 [0x103bcfc80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f9326005048>\n)}}",
    "<CFRunLoopObserver 0x600000320960 [0x103bcfc80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1043636b3), context = <CFRunLoopObserver context 0x6040000cbfa0>}",
    "<CFRunLoopObserver 0x600000320500 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x103dacda1), context = <CFRunLoopObserver context 0x7f9325f009d0>}",
    "<CFRunLoopObserver 0x604000320820 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10994b4ce), context = <CFRunLoopObserver context 0x0>}",
    "<CFRunLoopObserver 0x600000320460 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x103dace1c), context = <CFRunLoopObserver context 0x7f9325f009d0>}",
    "<CFRunLoopObserver 0x6000003203c0 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = <CFArray 0x60000024a980 [0x103bcfc80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f9326005048>\n)}}"
),
    timers = (null),
    currently 555851966 (37512520606194) / soft deadline in: 1.84467066e+10 sec (@ -1) / hard deadline in: 1.84467066e+10 sec (@ -1)
},

    3 : <CFRunLoopMode 0x604000197350 [0x103bcfc80]>{name = GSEventReceiveRunLoopMode, port set = 0x2c03, queue = 0x604000340840, source = 0x604000197420 (not fired), timer port = 0x2d03, 
    sources0 = <CFBasicHash 0x6040004465a0 [0x103bcfc80]>{type = mutable set, count = 1,
entries =>
    0 : <CFRunLoopSource 0x60400017d880 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x108b1a75a)}}
}
,
    sources1 = <CFBasicHash 0x6040004465d0 [0x103bcfc80]>{type = mutable set, count = 1,
entries =>
    2 : <CFRunLoopSource 0x60400017d940 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x5303, callout = PurpleEventCallback (0x108b1cbf7)}}
}
,
    observers = (null),
    timers = (null),
    currently 555851966 (37512522281056) / soft deadline in: 1.84467066e+10 sec (@ -1) / hard deadline in: 1.84467066e+10 sec (@ -1)
},

    4 : <CFRunLoopMode 0x604000196cd0 [0x103bcfc80]>{name = kCFRunLoopDefaultMode, port set = 0x2403, queue = 0x604000340580, source = 0x604000196da0 (not fired), timer port = 0x1b03, 
    sources0 = <CFBasicHash 0x604000446510 [0x103bcfc80]>{type = mutable set, count = 4,
entries =>
    0 : <CFRunLoopSource 0x60400017d880 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x108b1a75a)}}
    1 : <CFRunLoopSource 0x60000017d7c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000340420, callout = __handleEventQueue (0x1046dabb2)}}
    2 : <CFRunLoopSource 0x60400017f140 [0x103bcfc80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040000acde0, callout = FBSSerialQueueRunLoopSourceHandler (0x10828582f)}}
    4 : <CFRunLoopSource 0x60000017e540 [0x103bcfc80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600000249c60, callout = __handleHIDEventFetcherDrain (0x1046dabbe)}}
}
,
    sources1 = <CFBasicHash 0x604000446540 [0x103bcfc80]>{type = mutable set, count = 3,
entries =>
    0 : <CFRunLoopSource 0x60000017e9c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 15627, subsystem = 0x104ef6fe8, context = 0x0}}
    1 : <CFRunLoopSource 0x60000017d100 [0x103bcfc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42755, subsystem = 0x104f11668, context = 0x600000036e00}}
    2 : <CFRunLoopSource 0x60400017d7c0 [0x103bcfc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x5303, callout = PurpleEventCallback (0x108b1cbf7)}}
}
,
    observers = (
    "<CFRunLoopObserver 0x600000320320 [0x103bcfc80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = <CFArray 0x60000024a980 [0x103bcfc80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f9326005048>\n)}}",
    "<CFRunLoopObserver 0x600000320960 [0x103bcfc80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1043636b3), context = <CFRunLoopObserver context 0x6040000cbfa0>}",
    "<CFRunLoopObserver 0x600000320500 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x103dacda1), context = <CFRunLoopObserver context 0x7f9325f009d0>}",
    "<CFRunLoopObserver 0x604000320820 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10994b4ce), context = <CFRunLoopObserver context 0x0>}",
    "<CFRunLoopObserver 0x600000320460 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x103dace1c), context = <CFRunLoopObserver context 0x7f9325f009d0>}",
    "<CFRunLoopObserver 0x6000003203c0 [0x103bcfc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = <CFArray 0x60000024a980 [0x103bcfc80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f9326005048>\n)}}"
),
    timers = <CFArray 0x6000000ae640 [0x103bcfc80]>{type = mutable-small, count = 1, values = (
    0 : <CFRunLoopTimer 0x60000017db80 [0x103bcfc80]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 555851967 (1.296345 @ 37513821454379), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x1029f1849 / 0x10426e31b) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x6000002779c0>}
)},
    currently 555851966 (37512522394679) / soft deadline in: 1.29905968 sec (@ 37513821454379) / hard deadline in: 1.2990595 sec (@ 37513821454379)
},

    5 : <CFRunLoopMode 0x604000197900 [0x103bcfc80]>{name = kCFRunLoopCommonModes, port set = 0x3713, queue = 0x604000340bb0, source = 0x6040001979d0 (not fired), timer port = 0x4307, 
    sources0 = (null),
    sources1 = (null),
    observers = (null),
    timers = (null),
    currently 555851966 (37512525202454) / soft deadline in: 1.84467066e+10 sec (@ -1) / hard deadline in: 1.84467066e+10 sec (@ -1)
},

}
}

3.子线程创建RunLoop

- (void)viewDidLoad {
    [super viewDidLoad];
    NSRunLoop *loop1 = [NSRunLoop mainRunLoop];
    NSLog(@"\nloop1:%p=",loop1);
    
    // 创建子线程
    NSThread * thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];
    
}
- (void)run {
    NSLog(@"currentThread--%@",[NSThread currentThread]);
    // 获取当前线程对应的RunLoop,
    // currentRunLoop 方法是懒加载,如果当前线程已经有对应的RunLoop,直接取,没有就创建
    NSLog(@"runloop--%p", [NSRunLoop currentRunLoop]);
}
                                          /***打印结果*****/
2018-08-13 19:57:46.458913+0800 OC_Prac[27741:1769374] 
loop1:0x6040000bdb20=
2018-08-13 19:57:46.459731+0800 OC_Prac[27741:1769526] currentThread--<NSThread: 0x604000278d00>{number = 3, name = (null)}
2018-08-13 19:57:46.460673+0800 OC_Prac[27741:1769526] runloop--0x6040000bec60

RunLoop 相关类(Core Foundation)解析

共有五个类

CFRunLoopRef // 主要类
CFRunLoopModeRef      // 运行模式
CFRunLoopSourceRef   // 事件
CFRunLoopTimerRef     // 定时器
CFRunLoopObserverRef  // 监听

mode 运行模式简介

运行模式结构图

运行模式分类

NSTimer的两种创建方式

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(timer2) object:nil] start];
   
}

- (void)timer2
{
    NSRunLoop * runloop = [NSRunLoop currentRunLoop];
    // 创建定时器
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:true];
    [runloop run];
    // 另外两种开启RunLoop的方式
//    runloop runUntilDate:<#(nonnull NSDate *)#>
//    runloop runMode:<#(nonnull NSRunLoopMode)#> beforeDate:<#(nonnull NSDate *)#>
    
}

- (void)timer
{
    NSLog(@"_____%@",[NSThread currentThread]);
}

GCD定时器

NSTimer创建的定时器,可能会受到运行循环RunLoop的影响,所以这里介绍一个GCD的定时器,创建步骤请看代码

/**
     创建定时器
      参数1:DISPATCH_SOURCE_TYPE_TIMER GCD中的宏,表示定时器
      参数2:0 GCD中创建定时器以外的方法需要,记录描述信息
      参数3:0 GCD中创建定时器以外的方法需要,记录详细的描述信息
      参数4:队列,决定定时器执行在哪个线程,这里我们传入全局并发队列
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    /**
     设置定时器
     参数1:上个方法创建好的time
     参数2:从什么时候开始计时 DISPATCH_TIME_NOW表示马上开始计时
     参数3:时间间隔【NSEC_PER_SEC是 10的9次方,因为GCD中的时间代为为纳秒,* NSEC_PER_SEC 相当于秒与纳秒的一个换算】
     参数4:精确度 0为绝对精确
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    /*
     定时器要执行的操作
     */
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"-----%@",[NSThread currentThread]);
    });
    /*
     开启定时器
     */
    dispatch_resume(timer);
}

但是此时运行后定时器不能运行,原因是定时器是局部变量,当2秒中执行的操作时,viewDidLoad方法已经执行完,定时器作为局部变量已经被释放,故不能执行。
解决办法是用一个属性对其进行持有

@interface ViewController ()
@property (nonatomic,strong) dispatch_source_t timer;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    /**
     创建定时器
      参数1:DISPATCH_SOURCE_TYPE_TIMER GCD中的宏,表示定时器
      参数2:0 GCD中创建定时器以外的方法需要,记录描述信息
      参数3:0 GCD中创建定时器以外的方法需要,记录详细的描述信息
      参数4:队列,决定定时器执行在哪个线程,这里我们传入全局并发队列
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    /**
     设置定时器
     参数1:上个方法创建好的time
     参数2:从什么时候开始计时 DISPATCH_TIME_NOW表示马上开始计时
     参数3:时间间隔【NSEC_PER_SEC是 10的9次方,因为GCD中的时间代为为纳秒,* NSEC_PER_SEC 相当于秒与纳秒的一个换算】
     参数4:精确度 0为绝对精确
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    /*
     定时器要执行的操作
     */
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"-----%@",[NSThread currentThread]);
    });
    /*
     开启定时器
     */
    dispatch_resume(timer);
    
    self.timer = timer;
   
}

演示mode的使用方式

  1. 创建定时器,然后创建RunLoop,然后设定mode为NSDefaultRunLoopMode(即default模式)
- (void)viewDidLoad
{
    [super viewDidLoad];
    // 创建定时器
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:true];
    // 将定时器添加到RunLoop
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
}

- (void)timer
{
    NSLog(@"_____%@",[NSThread currentThread]);
}

打印结果

2018-08-14 21:01:27.941845+0800 OC_Prac[40412:1884454] _____<NSThread: 0x604000262740>{number = 1, name = main}
2018-08-14 21:01:29.941728+0800 OC_Prac[40412:1884454] _____<NSThread: 0x604000262740>{number = 1, name = main}
2018-08-14 21:01:31.941721+0800 OC_Prac[40412:1884454] _____<NSThread: 0x604000262740>{number = 1, name = main}
2018-08-14 21:01:33.941811+0800 OC_Prac[40412:1884454] _____<NSThread: 0x604000262740>{number = 1, name = main}
2018-08-14 21:01:35.940838+0800 OC_Prac[40412:1884454] _____<NSThread: 0x604000262740>{number = 1, name = main}
2018-08-14 21:01:37.941366+0800 OC_Prac[40412:1884454] _____<NSThread: 0x604000262740>{number = 1, name = main}
2018-08-14 21:01:39.941954+0800 OC_Prac[40412:1884454] _____<NSThread: 0x604000262740>{number = 1, name = main}
  1. 界面添加一个UIScrollView(或者以其为父类的控件),滑动时,定时器不在打印
    ============================ 释疑 ============================
    在界面滑动UIScrollView的时候,系统会将当前的NSDefaultRunLoopMode变为UITrackingRunLoopMode
    在原有的 NSDefaultRunLoopMode 模式下的定时器将不再工作,如果想在UITrackingRunLoopMode模式下定时器依旧工作,则需修改RunLoop的mode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

3.如果需要兼容两者,即不管UIScrollView 是否滑动时,计时器均工作有两种实现方式

[[NSRunLoop currentRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode: NSDefaultRunLoopMode];

原理【重点】

CFRunLoopMode 和 CFRunLoop 的结构大致如下:

struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array
    ...
};
  
struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
};

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

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

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


Source(事件)

Source值得是RunLoop需要处理的事件,比如触摸事件,定时器事件,Selector事件等等,其分类为:

Observer

CFRunLoopObserverRef是观察者,用于监听RunLoop的状态改变,RunLoop的几种状态如下

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),                  // 即将进入Runloop
    kCFRunLoopBeforeTimers = (1UL << 1),           // 即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2),          // 即将处理Source 
    kCFRunLoopBeforeWaiting = (1UL << 5),          // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),           // 即将从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),                   // 即将退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
 /**
     参数1.分配存储空间,使用默认即可
     参数2.要监听Runloop的状态,如果监听所以状态的话,kCFRunLoopAllActivities
     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),                  // 即将进入Runloop
     kCFRunLoopBeforeTimers = (1UL << 1),           // 即将处理Timer
     kCFRunLoopBeforeSources = (1UL << 2),          // 即将处理Source
     kCFRunLoopBeforeWaiting = (1UL << 5),          // 即将进入休眠
     kCFRunLoopAfterWaiting = (1UL << 6),           // 即将从休眠中唤醒
     kCFRunLoopExit = (1UL << 7),                   // 即将退出RunLoop
     kCFRunLoopAllActivities = 0x0FFFFFFFF
     };
     参数3.是否持续监听
     参数4.优先级,默认0
     参数5.回调
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities,true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入Runloop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理Timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理Source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"即将从休眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即将退出RunLoop");
                break;
                
            default:
                break;
        }
    });
    
    /**
     参数1:要监听的RunLoop
     参数2:观察者
     参数3:运行模式:传智要传C语言的k值
     NSDefaultRunLoopMode == kCFRunLoopDefaultMode
     NSRunLoopCommonModes == kCFRunLoopCommonModes
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer,kCFRunLoopDefaultMode);

通过代码看出,我们监听发主线程对应的RunLoop,打印结果如下

2018-08-16 21:40:00.005276+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:40:00.006718+0800 OC_Prac[5617:164857] 即将处理Sourc
2018-08-16 21:40:00.007225+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:41:00.002319+0800 OC_Prac[5617:164857] 即将从休眠中唤
2018-08-16 21:41:00.007482+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:41:00.009205+0800 OC_Prac[5617:164857] 即将处理Sourc
2018-08-16 21:41:00.010614+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:42:00.003428+0800 OC_Prac[5617:164857] 即将从休眠中唤
2018-08-16 21:42:00.006380+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:42:00.007469+0800 OC_Prac[5617:164857] 即将处理Sourc
2018-08-16 21:42:00.008467+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:43:00.001509+0800 OC_Prac[5617:164857] 即将从休眠中唤
2018-08-16 21:43:00.005709+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:43:00.007424+0800 OC_Prac[5617:164857] 即将处理Sourc
2018-08-16 21:43:00.007600+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:44:00.002512+0800 OC_Prac[5617:164857] 即将从休眠中唤
2018-08-16 21:44:00.004989+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:44:00.007427+0800 OC_Prac[5617:164857] 即将处理Sourc
2018-08-16 21:44:00.007811+0800 OC_Prac[5617:164857] 即将进入休眠

通过打印我们可以看出RunLoop的运行过程,处理timer,处理source,休眠,唤醒。。。最后休眠,此时如果点击按钮,RunLoop会被继续唤醒,以下为打印日志

2018-08-16 21:49:17.914762+0800 OC_Prac[5617:164857] 即将从休眠中唤醒
2018-08-16 21:49:17.914933+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:17.915044+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:17.953534+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:17.953739+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:17.959596+0800 OC_Prac[5617:164857] <UIButton: 0x7f94ffc0ac5
111); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x604000227f20>>
2018-08-16 21:49:17.960013+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:17.961225+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:17.961548+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:17.961652+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:17.961870+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:49:17.968598+0800 OC_Prac[5617:164857] 即将从休眠中唤醒
2018-08-16 21:49:17.968773+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:17.968871+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:17.968955+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:49:18.438397+0800 OC_Prac[5617:164857] 即将从休眠中唤醒
2018-08-16 21:49:18.438711+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:18.439179+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:18.439430+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:49:18.439870+0800 OC_Prac[5617:164857] 即将从休眠中唤醒
2018-08-16 21:49:18.440124+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:18.440303+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:18.440411+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:49:18.440545+0800 OC_Prac[5617:164857] 即将从休眠中唤醒
2018-08-16 21:49:18.440724+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:18.440881+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:18.441269+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:49:18.441691+0800 OC_Prac[5617:164857] 即将从休眠中唤醒
2018-08-16 21:49:18.441888+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:18.442097+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:18.442433+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:49:27.958017+0800 OC_Prac[5617:164857] 即将从休眠中唤醒
2018-08-16 21:49:27.958323+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:49:27.958543+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:49:27.958770+0800 OC_Prac[5617:164857] 即将进入休眠
2018-08-16 21:50:00.001717+0800 OC_Prac[5617:164857] 即将从休眠中唤醒
2018-08-16 21:50:00.003785+0800 OC_Prac[5617:164857] 即将处理Timer
2018-08-16 21:50:00.004011+0800 OC_Prac[5617:164857] 即将处理Source
2018-08-16 21:50:00.004618+0800 OC_Prac[5617:164857] 即将进入休眠

RunLoop的运行过程

RunLoop运行过程官方版
官方版处理逻辑
RunLoop运行过程网络版

从上图我们提炼出一些过程:
1.线程和RunLoop是一一对应的关系

  1. 具体运行过程
  1. 源码分析

CFRunLoopRun(void)主要方法

// 用DefaultMode启动
/// RunLoop的主函数,从里面的 do while 可以看出是一个循环
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        /**
           具体处理RunLoop的运行情况
         参数1:当前的运行循环
         参数2:当前运行模式
         参数3:运行时间【1.0e10 10的九次方,基本是forever】
         参数4:回调处理
         */
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
        // 停止循环的条件:
        // 1.RunLoop已经结束,比超时
        // 2.RunLoop停止状态,这是两种不同的状态
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

CFRunLoopRunSpecific

/// RunLoop的具体实现
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // 根据传入的参数,查找运行模式
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // 查找该运行模型里有没有source 或者 timer,如果没有RunLoop就会退出
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    Boolean did = false;
    if (currentMode) __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopUnlock(rl);
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;
    // 如果有source或者timer,就启动RunLoop
    // 先送通知,即将进入RunLoop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

__CFRunLoopRun 跳转

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();

    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
    return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
    rlm->_stopped = false;
    return kCFRunLoopRunStopped;
    }
    
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        if (!modeQueuePort) {
            CRASH("Unable to get port for run loop mode queue (%d)", -1);
        }
    }
#endif
    
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
    dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
    timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
    timeout_context->ds = timeout_timer;
    timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
    timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
    dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
    dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        dispatch_resume(timeout_timer);
    } else { // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }

    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;
#endif
        uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
        HANDLE livePort = NULL;
        Boolean windowsMessageReceived = false;
#endif
    __CFPortSet waitSet = rlm->_portSet;

        __CFRunLoopUnsetIgnoreWakeUps(rl);

        if (rlm->_observerMask & kCFRunLoopBeforeTimers)
            // 2.通知Observers: RunLoop 即将出发 Timer 回调
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources)
            // 3.通知Observer: RunLoop 即将触发Source 回调
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

    __CFRunLoopDoBlocks(rl, rlm);
        // 4.RunLoop 触发Source0(非port) 回调
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 执行加入的block
            __CFRunLoopDoBlocks(rl, rlm);
    }

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        // 如果没有Source1(基于port)处于 ready 状态,直接处理该Source1 ,然后跳转消息
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg = (mach_msg_header_t *)msg_buffer;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                goto handle_msg;
            }
#endif
        }

        didDispatchPortLastTime = false;
        // 通知Observer: RunLoop的线程即将进入休眠(Sleep)
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    __CFRunLoopSetSleeping(rl);
    // do not do any user callouts after this point (after notifying of sleeping)

        // Must push the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced.

        __CFPortSetInsert(dispatchPort, waitSet);
        
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);

        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        do {
            if (kCFUseCollectableAllocator) {
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    // Leave livePort as the queue port, and service timers below
                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                // Go ahead and leave the inner loop.
                break;
            }
        } while (1);
#else
        if (kCFUseCollectableAllocator) {
            // objc_clear_stack(0);
            // <rdar://problem/16393959>
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
        
        
#elif DEPLOYMENT_TARGET_WINDOWS
        // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
        __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);

        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

        // Must remove the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced. Also, we don't want them left
        // in there if this function returns.

        __CFPortSetRemove(dispatchPort, waitSet);
        
        __CFRunLoopSetIgnoreWakeUps(rl);

        // user callouts now OK again
    __CFRunLoopUnsetSleeping(rl);
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);

#if DEPLOYMENT_TARGET_WINDOWS
        if (windowsMessageReceived) {
            // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
            __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);

            if (rlm->_msgPump) {
                rlm->_msgPump();
            } else {
                MSG msg;
                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
            
            __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        sourceHandledThisLoop = true;
            
            // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
            // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
            // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
            __CFRunLoopSetSleeping(rl);
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);            
            __CFRunLoopUnsetSleeping(rl);
            // If we have a new live port then it will be handled below as normal
        }
        
        
#endif
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
            // Always reset the wake up port, or risk spinning forever
            ResetEvent(rl->_wakeUpPort);
#endif
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
#if USE_MK_TIMER_TOO
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
            // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
            void *msg = 0;
#endif
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            
            // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);

            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *reply = NULL;
        sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        if (NULL != reply) {
            (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
            CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
        }
#elif DEPLOYMENT_TARGET_WINDOWS
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
        }
            
            // Restore the previous voucher
            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
            
        } 
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
        
    __CFRunLoopDoBlocks(rl, rlm);
        

    if (sourceHandledThisLoop && stopAfterHandle) {
        retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
        retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
        retVal = kCFRunLoopRunFinished;
    }
        
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
#endif

    } while (0 == retVal);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

RunLoop 应用

常驻线程

  1. 场景:当一个线程执行完一个任务,正常来讲,线程会退出销毁,能否线程执行完一个任务不退出销毁,还再去执行另外一个任务
  2. 基础代码:先创建一个线程,然后执行task1,然后点击按钮使其执行task2
@interface ViewController ()
@property (nonatomic,strong) NSThread *thread;
@property (weak, nonatomic) IBOutlet UIButton *button;
@end

@implementation ViewController
- (IBAction)btnClick:(UIButton*)sender {
    NSLog(@"将要执行Task2");
    [self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:true];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(task1) object:nil];
    [self.thread start];
}


- (void)task1
{
    NSLog(@"\ncurrent---task1%@",[NSThread currentThread]);
}

- (void)task2
{
    NSLog(@"\ncurrent---task2%@",[NSThread currentThread]);
}

  1. 打印结果如下,并且崩溃
操作过程
  1. 通过RunLoop去实现
    思路: 我们在线程执行的方法中去开启一个RunLoop,绑定线程,修改task1方法的代码·
- (void)task1
{
    NSLog(@"\ncurrent---task1");
    self.loopTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:true];
    NSRunLoop *rl = [NSRunLoop currentRunLoop];
}

我们运行代码,发先不能达到效果,重新一下知识点:

- (void)task1
{
    NSLog(@"\ncurrent---task1");
    self.loopTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:true];
    NSRunLoop *rl = [NSRunLoop currentRunLoop];
    [rl run];
}

发现依旧不行,在回顾一下,RunLoop 的mode必须至少一个Source 或者 Timer才能运行,否则RunLoop退出,我们先为其添加timer

- (void)task1
{
    NSLog(@"\ncurrent---task1");
    self.loopTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:true];
    NSRunLoop *rl = [NSRunLoop currentRunLoop];
    [rl addTimer: self.loopTimer forMode:NSDefaultRunLoopMode];
    [rl run];
}

打印结果

2018-08-17 17:29:53.671863+0800 OC_Prac[14945:1101667] 
current---task1
2018-08-17 17:29:55.675755+0800 OC_Prac[14945:1101667] 执行_____time
2018-08-17 17:29:57.675658+0800 OC_Prac[14945:1101667] 执行_____time
2018-08-17 17:29:59.676740+0800 OC_Prac[14945:1101667] 执行_____time
2018-08-17 17:30:00.267080+0800 OC_Prac[14945:1101551] 将要执行Task2
2018-08-17 17:30:00.267576+0800 OC_Prac[14945:1101667] 
current---task2
2018-08-17 17:30:01.675727+0800 OC_Prac[14945:1101667] 执行_____time
2018-08-17 17:30:03.675899+0800 OC_Prac[14945:1101667] 执行_____time
2018-08-17 17:30:04.450278+0800 OC_Prac[14945:1101551] 将要执行Task2
2018-08-17 17:30:04.451242+0800 OC_Prac[14945:1101667] 
current---task2
2018-08-17 17:30:05.675903+0800 OC_Prac[14945:1101667] 执行_____time

通过打印结果可以看出,程序先执行了task1,然后执行timer,然后我们点击按钮,触发了task2,程序能正常运行,且没有崩溃

  1. 改善RunLoop实现
    我们为RunLoop添加定时器仅仅是为了让其mode完整,保证RunLoop不至于退出,这样做显然是太浪费了,我们改进一下,为其添加Source
- (void)task1
{
    NSLog(@"\ncurrent---task1");
    self.loopTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:true];
    NSRunLoop *rl = [NSRunLoop currentRunLoop];
    [rl addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [rl run];
}

依旧可以的达到效果

2018-08-17 18:08:30.223776+0800 OC_Prac[16264:1200765] 
current---task1
2018-08-17 18:08:36.429109+0800 OC_Prac[16264:1199953] 将要执行Task2
2018-08-17 18:08:36.429449+0800 OC_Prac[16264:1200765] 
current---task2
2018-08-17 18:08:37.298711+0800 OC_Prac[16264:1199953] 将要执行Task2
2018-08-17 18:08:37.300390+0800 OC_Prac[16264:1200765] 
current---task2

解释一下,参数port,这里可以回忆前面讲到的Source,在底层都是和Port相关,关于架构中port相关的东西,我们在后面细讲,此处只是创建一个通用的Port ---> [NSPort port]

自动释放池相关 --- 创建和销毁的时机

首先我还引入刚才RunLoop的运行过程图


RunLoop运行过程网络版
  • App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
  • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
  • 第二个 Observer 监视了两个事件:
    • BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() : 释放旧的池
    • _objc_autoreleasePoolPush() 并创建新池
    • Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。
      这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
  • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

runLoop 比较好的博客
博客

延伸一些面试题

面试题 面试题
上一篇下一篇

猜你喜欢

热点阅读