iOS RunLoop 深入探究

RunLoop是iOS或OSX开发中很重要的一个组件,它实现了很多底层的功能,本文主要分析RunLoop的概念、结构、运行原理,文中的结论主要是基于苹果官方文档和苹果开放RunLoop源码,尽量做得有理有据。

官方文档一 官方文档二 CoreFoundation源码

讲解内容:(一直不知道简书markdown有没有页内跳转功能,麻烦有知道的大佬告知一下) 一、Run Loop 总述 二、RunLoopMode 三、RunLoopSource 四、RunLoopTimer 五、RunLoopObserver 六、RunLoop核心 七、何时使用Run Loop

一、Run Loop 总述

RunLoop是实现线程基础结构中的一部分,它是一个处理事件的循环。RunLoop存在的意义是在有事件的时候使线程运作处理,没有事件的时候使线程休眠以节约内存资源。 RunLoop在代码当中的体现是一个类,有CF框架下的CFRunLoopRef和Cocoa框架下的NSRunLoopNSRunLoopCFRunLoopRef的面向对象封装。在讲解之前,有五个类需要了解:

CFRunLoopRef   
CFRunLoopModeRef (该类没有暴露在CFRunLoop.h文件,在CFRunLoop.c文件可见)
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

我们会看到诸如此类的声明typedef struct __CFRunLoop * CFRunLoopRef;,所以上面五个指针类型实际上对应的是下面五个结构体:

__CFRunLoop
__CFRunLoopMode 
__CFRunLoopSource
__CFRunLoopTimer
__CFRunLoopObserver

从官方文档可知:RunLoop会处理三种类型—— CFRunLoopSource、CFRunLoopTimer、CFRunLoopObserver,为了方便理解,先大致了解一下数据结构,直接查看CFRunLoop的结构:

struct __CFRunLoop {
    CFRuntimeBase _base;
    CFMutableSetRef _commonModes; 
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    此处省略其他项
};

先看_modes变量,它是一个集合,从字面意思可猜测里面关联的是CFRunLoopMode类型的实例(可查看实现代码)。然后,再来看CFRunLoopMode的数据结构:

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    此处省略其他项
};

_sources0_sources1关联的都是CFRunLoopSource实例,_observers关联的是CFRunLoopObserver实例,_timers关联的是CFRunLoopTimer实例。看到这里,它们基本的组织机构大致就如下所示了:

RunLoop组织结构图

获取runloop 苹果官方明确不允许我们创建runloop,只能通过 Cocoa中的:

@property (class, readonly, strong) NSRunLoop *currentRunLoop;
@property (class, readonly, strong) NSRunLoop *mainRunLoop;

类属性,和 CF 框架中的:

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

方法获取当前的 CFRunLoopRef/NSRunLoop 对象。它们之间不是 toll-free bridged 的,但是可以用 NSRunLoop 的实例方法- (CFRunLoopRef)getCFRunLoop获取 CFRunLoopRef 对象,由于它们引用的是同一个运行循环,所以完全可以混合使用它们。

下面我们分析一下 CF 框架下这两个方法的源码(可见CFRunloop框架是基于pthread的):

CFRunLoopRef CFRunLoopGetMain(void) {
    _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
    return _CFRunLoopGet0(pthread_self());
}

可见,它们的落脚点都是同一个方法(考虑篇幅,代码有删减):

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //__CFRunLoops是一个字典,从下面的代码中可知key和value分别是pthread指针和runloop
    if (!__CFRunLoops) {
    /*如果__CFRunLoops不存在,说明第一次进入(因为__CFRunLoops是用static修饰的),
为主线程创建一个runloop,并且加入静态字典__CFRunLoops
    */
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    }
    //获取t线程的runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
    //如果t线程的runloop不存在,创建一个
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
    //再获取一次,以保证创建成功
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
    //如果创建成功,赋值
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
    }
    return loop;
}

逻辑一目了然,当然关于内存释放问题和线程安全问题的处理需要去看源码,我这个是伪代码懂意思就行。

启动RunLoop

NSRunLoop 类

//启动RunLoop
- (void)run; 
//启动RunLoop直到超时
- (void)runUntilDate:(NSDate *)limitDate;
//在对应mode中启动RunLoop直到超时。若执行一次过后RunLoop就退出(高优先级)
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;

CFRunLoopRef 类

//在指定mode中启动RunLoop直到超时,returnAfterSourceHandled是指是否第一次处理了事件RunLoop就终止
CF_EXPORT CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);

很明显,NSRunLoop下的方法应该是 CFRunLoopRef 下方法的封装。

接下来详细讲解CFRunloop的其他组成部分:

二、RunLoopMode

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    此处省略其他项
};

重点:

每一个 source, timer, 或 observer 都要和 RunLoop 其中一个及其以上的 mode 关联才能有效。每次 RunLoop 启动的时候,都要指定一个特定 mode ,RunLoop 只处理该 mode 相关联的 source, timer, 或 observer。

主线程的 RunLoop 中系统自动配置了两个对我们有意义的 mode:

  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode):这个是默认的mode,大部分的事件会在该mode下执行。
  • UITrackingRunLoopMode:用于追踪ScrollView触摸滑动,也就是说在滑动ScrollView的时候主线程的mode会自动切换至该mode

在 CFRunLoop 类中,有一个 _commonModes 集合,还有一个 _commonModeItems 集合,习惯上,我们也称 source, timer, 或 observer 为 modeItem 。所以,这两个集合分别关联什么类型对象一目了然(主线程 RunLoop 的 kCFRunLoopDefaultMode/NSDefaultRunLoopMode 和 UITrackingRunLoopMode 都是被 CommonModes 标记的 mode)。那么,这两个集合有什么用呢?

Core Foundation定义了一种公共模式,当 RunLoop 的状态发生变化时,它会将 _commonModeItems 里的 source, timer, 或 observer 自动同步到 _commonModes 关联的所有 mode。这是一种打破常规的方式,对于这里的理解看了 “四、RunLoopTimer” 就会理解更深刻。

CFRunLoopMode 类并没有暴露在 CFRunLoop.h 文件,但是我们可以通过暴露在.h文件中的方法间接的操作 mode (操作mode都是用的它的名字)。

注意:值得一提的是,操作mode的方法中,如果名为参数modeName的mode不存在,内部会为你创建一个mode。从api来看,没有删除mode的方法,由此可见苹果不希望我们这么做。

//向runLoop的commonModes关联一个mode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    CHECK_FOR_FORK();
    //如果runloop释放了,函数返回
    if (__CFRunLoopIsDeallocating(rl)) return;
    __CFRunLoopLock(rl);  //加锁
    //如果commonMode里面不包含这个modeName
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {       
    //如果rl中_commonModeItems有值,那就都拷贝出来
    CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
    //把modeName添加进_commonModes
    CFSetAddValue(rl->_commonModes, modeName);
    if (NULL != set) {
        CFTypeRef context[2] = {rl, modeName};
        /* add all common-modes items to new mode 将所有common-modes items添加到新mode中 */
        CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
        CFRelease(set);
    }
    } else {
    }
    __CFRunLoopUnlock(rl);  //解锁
}
//返回当前mode的名字
CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl) {
    CHECK_FOR_FORK();
    CFStringRef result = NULL;
    __CFRunLoopLock(rl);  //加锁
    if (NULL != rl->_currentMode) {
         //如果_currentMode不为空,引用计数加1赋值给result(注意这里是浅拷贝)
        result = (CFStringRef)CFRetain(rl->_currentMode->_name);
    }
    __CFRunLoopUnlock(rl);  //解锁
    return result;
}
//返回所有mode名字的数组
CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl) {
    CHECK_FOR_FORK();
    CFMutableArrayRef array;
    __CFRunLoopLock(rl); //加锁
    //初始化等长数组
    array = CFArrayCreateMutable(kCFAllocatorSystemDefault, CFSetGetCount(rl->_modes), &kCFTypeArrayCallBacks);
    //对p1里面的每个对象,都执行一次p2方法,p2方法的参数是p1元素和p3
    CFSetApplyFunction(rl->_modes, (__CFRunLoopGetModeName), array);
    __CFRunLoopUnlock(rl); //解锁
    return array;
}

当然还有添加 mode Items 的一系列方法:

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

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

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

三、RunLoopSource

先通过官方的一张图切入:

图中可以看出,官方把“源”分为 input sourcestimer sources ,input sources 在代码中的体现就是 __CFRunLoopSource 结构体,接下来我们讨论这个 input sources, timer sources 在下一节讨论。

CFRunLoopSource 可以理解为“事件”,“事件”的源类型取决于输入源的类型,通常情况下,有两种源类型,一种是 port-based input sources,一种是 custom input sources,它们分别对应 __CFRunLoopMode 结构体中的 _sources1 和 _sources0 集合,下面是它们的解释:

  • port-based input sources (_sources1):它监视mach端口,包含一个mach_port,信号由内核通过对应端口自动发出唤醒runLoop。
  • custom input sources (_sources0):它监视自定义的事件,信号需要另一个线程手动发出唤醒runLoop。

特别的:Cocoa定义了一种 custom input sources,允许你在任何线程执行选择器,它减少了在同一线程执行同步任务的许多问题(这个我还没考证,有兴趣去查阅一下资料)。当你指定某一线程执行选择器时,目标线程必须存在 RunLoop,也就是说,如果目标线程是你通过 pthread 或者 NSThread 创建的,你需要将它们的 RunLoop激活(具体方法见第一节:RunLoop总述)。每次循环时 RunLoop 会循环执行所有队列中的方法选择器。开心的是,我们直接通过performSelectorOnMainThread:withObject:waitUntilDone:等方法直接使用,而不用关心内部实现。

关于 input sources 的创建,可以去查看官方文档,关于mach在 "六、RunLoop核心" 有简单说明。

四、RunLoopTimer

由于篇幅有限,这里不探讨 CFRunLoopTimer 的底层实现原理。

CFRunLoopTimerRef 就是 timer sources,CFRunLoopTimerRef 和 NSTimer 是 toll-free bridged 的,可以混合使用。

CFRunLoopTimer 会在特定的时刻同步给指定的线程发送消息,虽然它是基于时间的通知(time-based),但是并不是实时的机制(real-time)。与input sources一样,CFRunLoopTimer 需要和特定的 mode 关联,如果当前 RunLoop 监视的 mode 不是该 mode,那么计时器也不会触发。当 RunLoop 处于对应 mode 的执行队列中时,就会触发 timer 的回调,然后 timer 会等待下一次运行循环来调用它,所以它不是 real-time mechanism。

关于 timer 的回调时间,官方有这么一段解释,理解起来有些难度,所以把它贴出来:

A repeating timer reschedules itself automatically based on the scheduled firing time, not the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so much that it misses one or more of the scheduled firing times, the timer is fired only once for the missed time period. After firing for the missed period, the timer is rescheduled for the next scheduled firing time. 我的理解是,timer 能配置回调一次或多次事件,回调时机是一开始就预定了的,比如5s、10s、15s,而不是通过前一次回调时机来确定的。如果在 RunLoop 运行中,在回调 timer 事件之前有太多的逻辑导致错过了一个或多个预定时间点,则 timer 仅回调一次。

一个实际例子:

timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(respondsToTimer:) userInfo:@"timer go" repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

将 timer 添加到 NSDefaultRunLoopMode ,当我们滑动 scrollview 的时候,timer将不会正常回调(由于主runLoop切换至了UITrackingRunLoopMode)。 如果我们想要在滑动 scrollview 时仍然要计时器生效,解决方法如下:

//方法一:
//将该timer同时加入NSDefaultRunLoopMode和UITrackingRunLoopMode
timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(respondsToTimer:) userInfo:@"timer go" repeats:YES]; 
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

//方法二:
//将该timer关联所有被__CFRunLoop的_commonModes标记的mode中去NSDefaultRunLoopMode和UITrackingRunLoopMode都被_commonModes标记过)
timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(respondsToTimer:) userInfo:@"timer go" repeats:YES]; 
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

//方法三:
//使用CFRunLoop的api,将timer关联到_commonModes
timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(respondsToTimer:) userInfo:@"timer go" repeats:YES];
CFRunLoopTimerRef ref_timer = (__bridge CFRunLoopTimerRef)timer;
CFRunLoopAddTimer(CFRunLoopGetCurrent(), ref_timer, kCFRunLoopCommonModes);

大概看一下 CFRunLoopAddTimer()方法的实现:

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    ......
    if (modeName == kCFRunLoopCommonModes) {
        //拷贝一份commonModes集合
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
    if (NULL == rl->_commonModeItems) {
            //将_commonModeItems置空
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
        //将新的timer加入到_commonModeItems
    CFSetAddValue(rl->_commonModeItems, rlt);
    if (NULL != set) {
        CFTypeRef context[2] = {rl, rlt};
        //将item添加到所有_commonModes包含的 mode中
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
        CFRelease(set);
    }
    ......

五、RunLoopObserver

struct __CFRunLoopObserver {
    CFOptionFlags _activities;      /* immutable */
    CFRunLoopObserverCallBack _callout; /* immutable */
    此处省略其他
};

RunLoopObserver可以理解为一个监听器,它可以回调 RunLoop工作的各个步骤,回调中有一个枚举类型 CFRunLoopActivity,各项意义如下:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),  //进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1),  //即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2),  //即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5),  //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),  //已经在休眠中唤醒
    kCFRunLoopExit = (1UL << 7),  //退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

创建 CFRunLoopObserverRef 使用下面两个方法:

CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(......);
CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreateWithHandler(......);
//参数中有block回调

六、RunLoop核心

官方文档有一个流程说明:

  1. Notify observers that the run loop has been entered.
  2. Notify observers that any ready timers are about to fire.
  3. Notify observers that any input sources that are not port based are about to fire.
  4. Fire any non-port-based input sources that are ready to fire.
  5. If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.
  6. Notify observers that the thread is about to sleep.
  7. Put the thread to sleep until one of the following events occurs:
  • An event arrives for a port-based input source.
  • A timer fires.
  • The timeout value set for the run loop expires.
  • The run loop is explicitly woken up.
  1. Notify observers that the thread just woke up.
  2. Process the pending event.
  • If a user-defined timer fired, process the timer event and restart the loop. Go to step 2.
  • If an input source fired, deliver the event.
  • If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.
  1. Notify observers that the run loop has exited.

RunLoop的核心代码在 int32_t __CFRunLoopRun(......) 方法中,对照官方文档的说明,在伪代码中简要分析:

//该步骤是在上一级调用实现的
//1、回调Observer:进入runLoop

static int32_t __CFRunLoopRun(......) {
   
    int retVal = 0;
    do {
        
        //2、回调Observer:即将处理timer
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        //3、回调Observer:即将处理source
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        //执行对应mode的事件
        __CFRunLoopDoBlocks(rl, rlm);
        
        //4、触发sources0(custom)回调
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        //5、如果有Source1 (port) 处于ready状态,直接处理这个Source1,跳到第9步
        msg = (mach_msg_header_t *)msg_buffer;
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        
        //6、回调Observer:RunLoop对应线程即将进入休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
       
        //7. 等待接受mach_port的消息,线程进入休眠, 直到被事件唤醒,如下:
        //(1)一个基于port的Source1事件
        //(2)一个timer fire
        //(3)runLoop设置的超时时间到了
        //(4)runLoop被显示唤醒
        __CFRunLoopServiceMachPort(......); //该方法的意义是等待接收来自内核发出的消息
        
        //8、回调Observers: 线程刚刚被唤醒了
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        //9、处理事件
    handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (MACH_PORT_NULL == livePort) {
            ......
        }
        ......
        //(1)处理timer
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        ......
        //(2)处理Source1
        else {
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
                mach_msg_header_t *reply = NULL;
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
            }
        }
        
        //执行事件
        __CFRunLoopDoBlocks(rl, rlm);
        
        //以下几种情况停止runLoop:指定需要处理完返回/runLoop超时/runLoop被动停止/内部没有需要处理的事件
        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;
}

//该步骤在外部方法处理
//10、回调observer:runLoop退出

该方法细节太多,很长有些难理解,我也是根据官方的步骤按图索骥,但是大概的流程还是明白。可以看出,RunLoop实际上就是一个do(while)循环,它的核心依赖于内核的mach port。下面大概说明一下操作系统的核心结构:

Darwin是操作系统的核心,有内核、驱动等组成部分。 其中XNU内核由Mach、BSD、IOKit组成。Mach提供了CPU调度、IPC (进程间通信)等服务,BSD提供了进程管理、文件系统、网络等服务,IOKit是一个面向对象的封装库。

操作Mach的函数api在<mach/message.h>中,RunLoop的核心方法就是mach_msg()函数,这个函数通过内核的Mach陷阱(mach_msg_trap())把自己从用户态陷入内核态,从而可以在没有人从port发送消息过来时挂起线程。关于Mach的使用的原理自行搜索,实际上开发中几乎用不到。

extern mach_msg_return_t    mach_msg(
                    mach_msg_header_t *msg,
                    mach_msg_option_t option,
                    mach_msg_size_t send_size,
                    mach_msg_size_t rcv_size,
                    mach_port_name_t rcv_name,
                    mach_msg_timeout_t timeout,
                    mach_port_name_t notify);

七、何时使用Run Loop

以下情况需要使用 RunLoop :

  • 使用 RunLoopSource 与其他线程通讯时
  • 在线程中使用计时器
  • 在 Cocoa 应用中使用 performSelector… 等方法
  • 需要保持线程活性执行周期任务

在实现系统的功能中,RunLoop有着很大的作用,是很多组件的幕后功臣。比如自动释放池(AutoreleasePool),事件(Event)传递给应用(响应链),定时器等。 iOS触摸事件响应链的知识可以看我的另一篇博客

例:常驻线程

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

- (void)testThread {
    @autoreleasepool {
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [runloop addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
        [runloop run];
    }
}

有些时候,我们需要创建一个独立的线程来处理某一类任务,这时候往往我们希望线程处于随时待命状态,为了让 RunLoop 不至于因为无 mode item 而退出,就添加一个port 在 RunLoop 中。

写在后面

其实 RunLoop 的知识比较多,越往深了看越底层,会涉及到操作系统的东西了。当然,是否需要了解更多看个人选择了,单单是 CFRunLoop.m 源代码就有接近4000行,全部看完也是够呛。作为一个iOS程序员,了解这些偏底层的东西是踏入高阶的必修课,其实很多看起来很恐怖的源码,只要采取先看苹果官方文档,再循序渐进理解的方式,终会豁然开朗。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

asp.net mvc本地程序集和GAC的程序集冲突解决方法

一个从asp.net mvc 3升级到asp.net mvc 4的项目发生了如下错误: [A]System.Web.WebPages.Razor.Config...

23050
来自专栏.NET开发者社区

(码友推荐)2018-07-06 .NET及相关开发资讯速递

1.Dotnet outdated helps you keep your projects up to date

9930
来自专栏张善友的专栏

Silverlight 2 的基础XAML语法学习

要想成为Silverlight 的开发高手,熟练通达XAML语法是必由之路。现在我们来一个空的XAML模板。 Silverlight 1.0 的模板是这样的...

20890
来自专栏walterlv - 吕毅的博客

分析现有 WPF / Windows Forms 程序能否顺利迁移到 .NET Core 3.0(使用 .NET Core 3.0 Desktop API Analyzer )

2018-09-13 12:59

14710
来自专栏陈满iOS

iOS开发·RunLoop源码与用法完全解析(输入源,定时源,观察者,线程间通信,端口间通信,NSPort,NSMessagePort,NSMachPort,NSPortMessage)

OSX / iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。

42220
来自专栏张善友的专栏

IronPython整合Windows PowerShell

      Windows PowerShell 是微软为 Windows 环境所开发的 shell 及脚本语言技术,这项全新的技术提供了丰富的控制与自动化的系...

24070
来自专栏ASP.NETCore

.NET Core中ADO.NET SqlClient的使用与常见问题

  在很多要求性能的项目中,我们都要使用传统的ADO.NET的方式来完成我们日常的工作;目前有一些网友问有关于.NET Core操作SQL Server的问题在...

32510
来自专栏iOS技术杂谈

iOS多线程——RunLoop与GCD、AutoreleasePool你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里

你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里 转载请注明出处 https://cloud.tencent.co...

585110
来自专栏逍遥剑客的游戏开发

DirectX in C++/CLI

29150
来自专栏张善友的专栏

Mono P/Invoke :DLLImport

Mono 的初衷是为了吸引更多的Windows .Net程序员来加入Linux平台的开发。但在Linux世界中C语言依然是主流。很多时候一些关键应用(比如大型笛...

337100

扫码关注云+社区

领取腾讯云代金券