Runloop【整理版】

概念

  1. 字面意思:就是转圈圈,运行循环
    类似于do ... while
  2. 基本作用:
  • 保持程序的持续运行
  • 处理app中各种事件(比如触摸事件,定时器事件,Selector事件)
  • 节省CPU资源,提高程序性能(该做事时做事,没事的时候休息)
  1. 对比通过循环调用
  • 虽然也可以让程序持续调用,但是比较耗费资源
  • runtime可以做到有任务时执行,没任务时休眠
  1. 官方资料
  • 官网版本
  • 翻译版本
  • 源码下载

API

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

  • Core Foundation(C)框架下的CFRunLoopRef
[NSRunLoop currentRunLoop]; // 获取当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获取主线程的RunLoop对象
  • Foundation(OC) 框架下的 NSRunloop,是对CFRunLoopRef的封装
CFRunLoopGetCurrent(); // 获取当前线程的RunLoop对象
CFRunLoopGetMain(); //  // 获取主线程的RunLoop对象

与线程的关系

  • 每条线程都有唯一的一个与之对应的RunLoop对象,其关系是保存在一个全局的 Dictionary 里
  • 主线程的RunLoop已经自动创建好,子线程的RunLoop需要主动创建,并且手动开启
  • 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] 
 ********{wakeup port = 0x1903, stopped = false, ignoreWakeUps = false, 
current mode = kCFRunLoopDefaultMode,
common modes = {type = mutable set, count = 2,
entries =>
    0 : {contents = "UITrackingRunLoopMode"}
    2 : {contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = {type = mutable set, count = 13,
entries =>
    0 : {signalled = No, valid = Yes, order = -1, context = {version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x108b1a75a)}}
    1 : {signalled = Yes, valid = Yes, order = 0, context = {version = 0, info = 0x6040000acde0, callout = FBSSerialQueueRunLoopSourceHandler (0x10828582f)}}
    4 : {valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1043636b3), context = }
    10 : {signalled = No, valid = Yes, order = 0, context =  {port = 15627, subsystem = 0x104ef6fe8, context = 0x0}}
    11 : {valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x103dacda1), context = }
    12 : {signalled = No, valid = Yes, order = -1, context = {version = 0, info = 0x600000340420, callout = __handleEventQueue (0x1046dabb2)}}
    13 : {valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = {type = mutable-small, count = 1, values = (
    0 : <0x7f9326005048>
)}}
    14 : {valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = {type = mutable-small, count = 1, values = (
    0 : <0x7f9326005048>
)}}
    15 : {valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x103dace1c), context = }
    16 : {valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10994b4ce), context = }
    20 : {signalled = No, valid = Yes, order = -2, context = {version = 0, info = 0x600000249c60, callout = __handleHIDEventFetcherDrain (0x1046dabbe)}}
    21 : {signalled = No, valid = Yes, order = 0, context =  {port = 42755, subsystem = 0x104f11668, context = 0x600000036e00}}
    22 : {signalled = No, valid = Yes, order = -1, context = {version = 1, info = 0x5303, callout = PurpleEventCallback (0x108b1cbf7)}}
}
,
modes = {type = mutable set, count = 4,
entries =>
    2 : {name = UITrackingRunLoopMode, port set = 0x1f03, queue = 0x6040003406e0, source = 0x6040001971b0 (not fired), timer port = 0x2a03, 
    sources0 = {type = mutable set, count = 4,
entries =>
    0 : {signalled = No, valid = Yes, order = -1, context = {version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x108b1a75a)}}
    1 : {signalled = No, valid = Yes, order = -1, context = {version = 0, info = 0x600000340420, callout = __handleEventQueue (0x1046dabb2)}}
    2 : {signalled = Yes, valid = Yes, order = 0, context = {version = 0, info = 0x6040000acde0, callout = FBSSerialQueueRunLoopSourceHandler (0x10828582f)}}
    4 : {signalled = No, valid = Yes, order = -2, context = {version = 0, info = 0x600000249c60, callout = __handleHIDEventFetcherDrain (0x1046dabbe)}}
}
,
    sources1 = {type = mutable set, count = 3,
entries =>
    0 : {signalled = No, valid = Yes, order = 0, context =  {port = 15627, subsystem = 0x104ef6fe8, context = 0x0}}
    1 : {signalled = No, valid = Yes, order = 0, context =  {port = 42755, subsystem = 0x104f11668, context = 0x600000036e00}}
    2 : {signalled = No, valid = Yes, order = -1, context = {version = 1, info = 0x5303, callout = PurpleEventCallback (0x108b1cbf7)}}
}
,
    observers = (
    "{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7f9326005048>\n)}}",
    "{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1043636b3), context = }",
    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x103dacda1), context = }",
    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10994b4ce), context = }",
    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x103dace1c), context = }",
    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = {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 : {name = GSEventReceiveRunLoopMode, port set = 0x2c03, queue = 0x604000340840, source = 0x604000197420 (not fired), timer port = 0x2d03, 
    sources0 = {type = mutable set, count = 1,
entries =>
    0 : {signalled = No, valid = Yes, order = -1, context = {version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x108b1a75a)}}
}
,
    sources1 = {type = mutable set, count = 1,
entries =>
    2 : {signalled = No, valid = Yes, order = -1, 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 : {name = kCFRunLoopDefaultMode, port set = 0x2403, queue = 0x604000340580, source = 0x604000196da0 (not fired), timer port = 0x1b03, 
    sources0 = {type = mutable set, count = 4,
entries =>
    0 : {signalled = No, valid = Yes, order = -1, context = {version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x108b1a75a)}}
    1 : {signalled = No, valid = Yes, order = -1, context = {version = 0, info = 0x600000340420, callout = __handleEventQueue (0x1046dabb2)}}
    2 : {signalled = Yes, valid = Yes, order = 0, context = {version = 0, info = 0x6040000acde0, callout = FBSSerialQueueRunLoopSourceHandler (0x10828582f)}}
    4 : {signalled = No, valid = Yes, order = -2, context = {version = 0, info = 0x600000249c60, callout = __handleHIDEventFetcherDrain (0x1046dabbe)}}
}
,
    sources1 = {type = mutable set, count = 3,
entries =>
    0 : {signalled = No, valid = Yes, order = 0, context =  {port = 15627, subsystem = 0x104ef6fe8, context = 0x0}}
    1 : {signalled = No, valid = Yes, order = 0, context =  {port = 42755, subsystem = 0x104f11668, context = 0x600000036e00}}
    2 : {signalled = No, valid = Yes, order = -1, context = {version = 1, info = 0x5303, callout = PurpleEventCallback (0x108b1cbf7)}}
}
,
    observers = (
    "{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7f9326005048>\n)}}",
    "{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1043636b3), context = }",
    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x103dacda1), context = }",
    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10994b4ce), context = }",
    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x103dace1c), context = }",
    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103d7dd92), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7f9326005048>\n)}}"
),
    timers = {type = mutable-small, count = 1, values = (
    0 : {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 = }
)},
    currently 555851966 (37512522394679) / soft deadline in: 1.29905968 sec (@ 37513821454379) / hard deadline in: 1.2990595 sec (@ 37513821454379)
},

    5 : {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--{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 运行模式简介

Runloop【整理版】_第1张图片
运行模式结构图
  • 一个运行模式(mode)要是至少包含一个Source 或者 一个Timer,Observe不是必须的
  • RunLoop启动后只能选择一种运行模型(即 CurrentMode),如果想切换运行模式,必须要先退出当前运行模式
  • 这样的设计好处是,通过运行模式分隔开不同组的 Source/Timer/Observer,让其互不影响。
  • Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

运行模式分类

  • kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
  • UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
  • kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 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));
  • [NSTimer timerWithXxx...];
    此种方式创建定时器,需要手动将其添加到RunLoop中,否则无法工作,以下例子中会进行演示
  • [NSTimer scheduledTimerWithXxx...];
    • 此种方式创建定时器会默认将其添加到RunLoop中,并且其mode为default(NSDefaultRunLoopMode)
    • 第二种方式需要注意的是,尽管系统会将定时器添加至RunLoop,但是如果是子线程我们还需要手动创建好RunLoop,并且启动RunLoop,因为子线程RunLoop需要手动创建和开始
- (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] _____{number = 1, name = main}
2018-08-14 21:01:29.941728+0800 OC_Prac[40412:1884454] _____{number = 1, name = main}
2018-08-14 21:01:31.941721+0800 OC_Prac[40412:1884454] _____{number = 1, name = main}
2018-08-14 21:01:33.941811+0800 OC_Prac[40412:1884454] _____{number = 1, name = main}
2018-08-14 21:01:35.940838+0800 OC_Prac[40412:1884454] _____{number = 1, name = main}
2018-08-14 21:01:37.941366+0800 OC_Prac[40412:1884454] _____{number = 1, name = main}
2018-08-14 21:01:39.941954+0800 OC_Prac[40412:1884454] _____{number = 1, name = main}
  1. 界面添加一个UIScrollView(或者以其为父类的控件),滑动时,定时器不在打印
    ============================ 释疑 ============================
    在界面滑动UIScrollView的时候,系统会将当前的NSDefaultRunLoopMode变为UITrackingRunLoopMode
    在原有的 NSDefaultRunLoopMode 模式下的定时器将不再工作,如果想在UITrackingRunLoopMode模式下定时器依旧工作,则需修改RunLoop的mode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
Runloop【整理版】_第2张图片

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

  • 将timer 同时添加在两种运行模式(mode)中
[[NSRunLoop currentRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode: NSDefaultRunLoopMode];
  • 添加占位的mode,即我们先前提到的 kCFRunLoopCommonModes,其在功能上等于以上两行代码,即将timer同时添加到UITrackingRunLoopModeNSDefaultRunLoopMode

原理【重点】

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事件等等,其分类为:

  • 早期分类:
    • Port_Based Sources
    • Custom Input Sources
    • Cocoa Perform Seletor Sources 【可 以理解为Selector方法】
  • 现在分类--是基于函数调用栈【函数调用栈见下图】
    • Source0: 非基于端口(port)的【可以理解为用户触发事件】
    • Source1: 基于端口(port)的 【可以理解为系统事件】


      Runloop【整理版】_第3张图片
      函数调用栈

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
};
  • 给RunLoop添加observer
    • 创建Observer
    • 将Observer和要监听的RunLoop添加到监听函数中
      对应的方法,即方法参数说明等见代码
 /**
     参数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] >
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【整理版】_第4张图片
RunLoop运行过程官方版

Runloop【整理版】_第5张图片
官方版处理逻辑

Runloop【整理版】_第6张图片
RunLoop运行过程网络版

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

  1. 具体运行过程
  • RunLoop的通知监听者,即将进入RunLoop便先向外发通知
  • 通知监听者将要去处理Timer事件,然后去处理
  • 通知监听者将要处理Source0,然后处理
  • 然后检查是否有Source1(Source1事件可以理解为是对Source1和Timer等事件的分发)事件,如果有就去处理(再从第2步开始循环),如果没有就休眠
  • 休眠过程中有事件唤醒,就跳转到第2步再去处理
  • 最终RunLoop会退出,退出前会发出通知,销毁的时机有:
    • 线程销毁
    • RunLoop运行时间到的时候,RunLoop是可以设置什么时候结束的
  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);
                // 
                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);
            // 
            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. 打印结果如下,并且崩溃
Runloop【整理版】_第7张图片
操作过程
  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];
}

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

  • 主线程的RunLoop系统已经创建好了,但是子线程的RunLoop要手动创建
  • 不到要手动创建,还要手动开启
  • 刚才代码的问题,并没有手动开启
- (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【整理版】_第8张图片
RunLoop运行过程网络版
  • 从图中整个运行过程来看,在第1步和第2步之间比较好,RunLoop进入,然后将要处理事件,可能创建很多变量对象之类的,此时创建释放池存储这些变量或者对象
  • 以上方案缺点在于释放池的创建会放在循环中,不太好,故
    • 第一次创建自动释放池的时机:是在第一步,即第一次启动RunLoop的时候
    • 销毁的时机: 是在RunLoop退出的时候
  • 由于在整个RunLoop运行过程中可能会产生大量的变量,故仅创建一个自动释放池不够用,也不方便处理,故其他的创建和销毁时机为:
    在第6步的时候,即将进入休眠的时候,会将之前循环内部创建的释放池销毁,并创建新的释放池
  • 关于autoReleasePool的一些其他解释
  • 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 比较好的博客
博客

延伸一些面试题

Runloop【整理版】_第9张图片
面试题
Runloop【整理版】_第10张图片
面试题

你可能感兴趣的:(Runloop【整理版】)