Runloop

26 Nov 2019

iOS 程序启动时会创建一个runloop这样才能响应app的各种交互事件。

iOS 有两套runloop相关的框架 Foundation: NSRunLoop Core Foundation: CFRunLoopRef

可以通过如下方式获取runloop

    // 获取 runloop对象
    // foundation
    NSRunLoop *mloop = [NSRunLoop mainRunLoop];
    NSRunLoop *loop = [NSRunLoop currentRunLoop];
    
    // core foundation
    CFRunLoopRef cloop = CFRunLoopGetCurrent();
    CFRunLoopRef cmloop = CFRunLoopGetMain();

runloop 与 线程的关系

查看CFRunLoopGetCurrent源码

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

// 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();
    }
    // 如果还没有创建存runloop的全局字典,先创建一个
    // 并创建主线程的runloop
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
	    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
	    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // 查找有没有对应线程的runloop
    // 没有就创建一个,并添加到用来存runloops的全局字典中
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
	CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
	loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	if (!loop) {
	    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
	    loop = newLoop;
	}
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
	CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

上面的代码验证前面三点

runloop 相关的类

Core Foundation中关于RunLoop的5个类

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
typedef struct __CFRunLoop * CFRunLoopRef;
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
typedef struct __CFRunLoopTimer * CFRunLoopTimerRef;

struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
//次要的属性
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* locked for accessing mode list */
    __CFPort _wakeUpPort;			// used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    uint32_t _winthread;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

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

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;			/* immutable */
    CFRunLoopObserverCallBack _callout;	/* immutable */
    CFRunLoopObserverContext _context;	/* immutable, except invalidation */
};

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;		/* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	/* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};

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

CFRunLoopModeRef

CFRunLoopObserverRef

runloop的不同状态

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   //进入runloop
    kCFRunLoopBeforeTimers = (1UL << 1), //即将进入timer
    kCFRunLoopBeforeSources = (1UL << 2), //即将进入Sources
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入等待
    kCFRunLoopAfterWaiting = (1UL << 6),  //等待结束
    kCFRunLoopExit = (1UL << 7),  //退出runloop
    kCFRunLoopAllActivities = 0x0FFFFFFFU 
};

添加observer来监听Runloop的状态

// observer状态回调
void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    // 打印当前的mode
    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
    NSLog(@"----mode: %@", mode);
    CFRelease(mode);
    
//    NSLog(@"runLoopObserverCallBack->%@, %@", observer, info);
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
        break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
        break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
        break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
        break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
        break;
        case kCFRunLoopAllActivities:
            NSLog(@"kCFRunLoopAllActivities");
        break;
    }
}
{
// 创建观察者
    // 使用函数来响应事件
    CFRunLoopObserverRef observer
    = CFRunLoopObserverCreate(kCFAllocatorDefault,
                              /*CFAllocatorRef allocator*/
                               kCFRunLoopEntry |
                              kCFRunLoopBeforeTimers |
                              kCFRunLoopBeforeSources |
                              kCFRunLoopBeforeWaiting |
                              kCFRunLoopAfterWaiting |
                              kCFRunLoopExit |
                              kCFRunLoopAllActivities,
                              /*CFOptionFlags activities*/
                              true,
                              /*Boolean repeats*/
                              0,
                              /*CFIndex order*/
                              &runLoopObserverCallBack,
                              /*CFRunLoopObserverCallBack callout*/
                              nil
                              /*CFRunLoopObserverContext *context*/
                              );
                                                            
    
    // 使用 block 来响应事件
    CFRunLoopObserverRef observer1
    = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,
                                         kCFRunLoopEntry |
                                         kCFRunLoopBeforeTimers |
                                         kCFRunLoopBeforeSources |
                                         kCFRunLoopBeforeWaiting |
                                         kCFRunLoopAfterWaiting |
                                         kCFRunLoopExit |
                                         kCFRunLoopAllActivities,
                                         true,
                                         0,
                                         ^(CFRunLoopObserverRef observer,
                                           CFRunLoopActivity activity) {
                                            
                                            // 监听mode切换
                                            switch (activity) {
                                                case kCFRunLoopEntry:
                                                {
                                                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                                                                                        
                                                    NSLog(@"----mode: %@", mode);
                                                    CFRelease(mode);
                                                    NSLog(@"kCFRunLoopEntry");
                                                }
                                                    break;
                                                case kCFRunLoopExit:
                                                {
                                                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                                                                                        
                                                    NSLog(@"----mode: %@", mode);
                                                    CFRelease(mode);
                                                    NSLog(@"kCFRunLoopExit");
                                                }
                                                break;
                                                case kCFRunLoopAllActivities:
                                                    NSLog(@"kCFRunLoopAllActivities");
                                                break;
                                                default:
                                                    break;
                                            }
        
                                        });
    
    // 添加runloop观察者
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer1, kCFRunLoopCommonModes);
    
    CFRelease(observer);
    CFRelease(observer1);
}

RunLoop 的运行逻辑

Runloop源码跟读

创建一个控制器,并在

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"");
}

中打一个断点,进入lldb调试模式,使用bt查看方法调用的栈

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010264236d MyRunloop`-[ViewController touchesBegan:withEvent:](self=0x00007fa496c04f40, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600002914280) at ViewController.m:154:5
    frame #1: 0x00007fff4787c885 UIKitCore`forwardTouchMethod + 340
    frame #2: 0x00007fff4787c720 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
    frame #3: 0x00007fff4788b93c UIKitCore`-[UIWindow _sendTouchesForEvent:] + 1867
    frame #4: 0x00007fff4788d524 UIKitCore`-[UIWindow sendEvent:] + 4596
    frame #5: 0x00007fff47868427 UIKitCore`-[UIApplication sendEvent:] + 356
    frame #6: 0x00007fff478e987e UIKitCore`__dispatchPreprocessedEventFromEventQueue + 6847
    frame #7: 0x00007fff478ec344 UIKitCore`__handleEventQueueInternal + 5980
    frame #8: 0x00007fff23bb2221 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #9: 0x00007fff23bb214c CoreFoundation`__CFRunLoopDoSource0 + 76
    frame #10: 0x00007fff23bb1924 CoreFoundation`__CFRunLoopDoSources0 + 180
    frame #11: 0x00007fff23bac62f CoreFoundation`__CFRunLoopRun + 1263
    frame #12: 0x00007fff23babe16 CoreFoundation`CFRunLoopRunSpecific + 438
    frame #13: 0x00007fff38438bb0 GraphicsServices`GSEventRunModal + 65
    frame #14: 0x00007fff4784fb48 UIKitCore`UIApplicationMain + 1621
    frame #15: 0x0000000102642704 MyRunloop`main(argc=1, argv=0x00007ffeed5bcd38) at main.m:18:12
    frame #16: 0x00007fff51a1dc25 libdyld.dylib`start + 1
    frame #17: 0x00007fff51a1dc25 libdyld.dylib`start + 1

可以看到进入runloop的第一个函数是CFRunLoopRunSpecific,下面查看源码

//循环进入runloop,使用CFRunLoopStop只能停止当前runloop,然后又会重新进入
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

// 让runloop进入指定的mode
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    int32_t result = kCFRunLoopRunFinished;
    // 通知Observer: 进入runloop
	__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // 具体要做的事情
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知observer: 退出runloop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}

// 下面的代码删掉了源码中比较复杂的逻辑,只保留关键的方法调用,方便理解runloop的运行流程
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

    int32_t retVal = 0;
    do {
        // 通知Observers: 即将处理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 通知Observers: 即将处理Source
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        // 处理block
        __CFRunLoopDoBlocks(rl, rlm);

        // 处理source0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 处理block
            __CFRunLoopDoBlocks(rl, rlm);
        }

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        // 判断有无Source1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // 如果有Source1, 就跳转到handle_msg
            goto handle_msg;
        }
        
        didDispatchPortLastTime = false;
        
        // 通知Observers: 即将休眠
        __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.


        // 等待别的消息来唤醒当前线程
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
            
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        // 通知Observers: 休眠结束
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    handle_msg:;
        if (timer唤醒) {
            // 处理timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }
        else if (GCD唤醒) {
            // 处理gcd相关的事情
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
            // 处理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;  
        } 
        // 处理 Blocks
        __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;
        }
    } while (0 == retVal);
    return retVal;
}

关于runloop的休眠:调用了系统内核的函数,让当前线程彻底休眠,只会占用少量的资源。当有触发事件发生时才会被唤醒。

Runloop 的应用

使用如下方法创建的timer会加入到当前runloop的default mode中

/// Creates and returns a new NSTimer object initialized with the
// specified block object and schedules it on the current run loop 
// in the default mode.
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        static int a = 0;
        CFRunLoopMode mode =  CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
        NSLog(@"%@:%d", mode, a++);
        CFRelease(mode);
}];

当界面滚动会进入tracking mode,这个时候timer会暂停, 可以使用下面的方法,添加到通用模式NSRunLoopCommonModes = UITrackingRunLoopMode | NSDefaultRunLoopMode
注意:NSRunLoopCommonModes 还可能包含其他模式,可以手动添加 CFRunLoopAddCommonMode(<CFRunLoopRef rl>, CFRunLoopMode mode)

NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            static int a = 0;
            CFRunLoopMode mode =  CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
            NSLog(@"%@:%d", mode, a++);
            CFRelease(mode);
    }];
    
//NSRunLoopCommonModes = UITrackingRunLoopMode | NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

新建一个线程

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[thread start];

线程执行完一个任务之后就会被回收,下次还需要处理任务时又需要重新创建一个新的线程,这样子比较消耗资源,所有在不需要并发的情况可以考虑让线程保活,使用同一个线程处理不同时间点需要处理的事务

self.stopRunloop = false;
__weak typeof(self) weakSelf = self;
// 如果不开启一个运行中的runloop,线程执行完一个事件时候就关闭
AYThread *thread = [[AYThread alloc] initWithBlock:^{
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
    
    // 线程保活,添加Port 或者 Timer,不然执行完这个函数线程就释放了.即使被强引用
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    
    //        static int count = 0;
    //        NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    //            NSLog(@"%d", count++);
    //        }];
    //        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    
    // 查看官方注释, 使用这种方法只要有source, runloop永远不会结束
    // [[NSRunLoop currentRunLoop] run];
    
    
    // 重复启动runloop让线程保活
    while (weakSelf && !weakSelf.isStopRunloop)
    {
        // 处理完一个事件之后,这个runloop就结束了,
        // 会进入while循环再次开启runloop等待事件
        NSLog(@"runloop run");
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}];
    
[thread start];
self.thread = thread;

让方法在这个线程上执行

// waitUntilDone:NO 直接继续执行下面的代码
// waitUntilDone:YES 等Selector执行完成后再执行后面的代码
// 使用同一个线程,处理串行任务
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];

在必要的时候停止这个线程的runloop,这样线程才能被释放

- (void)dealloc
{
    [self removeThread];
    NSLog(@"%s", __func__);
}

- (void)stopRunloop
{   
    // 进入子线程中,停止当前runloop
    CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)removeThread
{
    NSLog(@"%s", __func__);
    if (self.thread != nil)
    {
        self.stopRunloop = true;
        [self performSelector:@selector(stopRunloop) onThread:self.thread withObject:nil waitUntilDone:YES];
        self.thread = nil;
    }
}

线程保活的demo

reference: apple core foundation source