Runloop工作原理(源码分析)

Runloop定义

RunLoop是与线程相关的基础架构中的一部分,它是一个处理事件的循环(线程进入这个循环,运行事件处理程序来响应传入的事件),RunLoop的目的是当有事件需要处理时,线程是活跃的、忙碌的,当没有事件后,线程进入休眠。
注意一:RunLoop只是线程的其中一个模块。
注意二:RunLoop它是一个do...while死循环,但不是一个完全死循环(如果是完全死循环,占用内存会不断增加直到app崩溃,但我们的app写一个死循环内存一直保存在30%以下)。
关于Runloop可以下载CF源码

CF源码
CFRunLoop

RunLoop可以是一个对象,这个对象提供一个入口函数,让程序进入do...while循环。

官方文档上面搜索Runloop是搜不到的

Runloop官方文档

上面说了Runloop是线程的一个模块,我们可以通过搜索线程查找Runloop

文档1
文档2

RunLoop也可以是消息机制处理模式。任何的消息都会交给RunLoop进行处理,比如说点击屏幕,就是用户给app发送响应的消息,让app去响应。

举例RunLoop进行消息处理:

// 在ViewController.m 的 viewDidLoad
// 断点在NSLog,看线程调用
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"永远单身yyds");
}];
举例
// 这个就是关于处理timer的一种机制
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

Runloop源码里就有这个声明:

image.png
// 自行调试。
[self performSelector:@selector(open) withObject:nil afterDelay:1.0];

当然还有很多类型,比如说blockGCD等等。

void (^block)(void) = ^{       
    NSLog(@"aaaa");
};
block();
image.png

看看我们的app应用的Main Runloop入口:

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

看代码注释就解释了我们应用程序为什么会跑到AppDelegate在里面初始化主窗口

// If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no
// NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.
UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nullable argv[_Nonnull], NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);

didFinishLaunchingWithOptions里打断点,看函数调用堆栈,看看程序进入运行循环的过程:

调用堆栈

Runloop作用

1.保持线程持续运行(常驻线程,不让其退出销毁)
2.处理App中各种事件(触摸/定时器/performSelector)
3.节约cpu资源。该做事就做事,该休息就休息

线程与Runloop

先看一段源码CFRunLoopGetMain

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

拿到mainThread对象

#if DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_IPHONESIMULATOR
CF_EXPORT pthread_t _CF_pthread_main_thread_np(void);
#define pthread_main_thread_np() _CF_pthread_main_thread_np()
#endif

获取mainRunloop

image.png

_CFRunLoopGet0(pthread_main_thread_np());
其中看出来mainRunloop是根据mainThread去获取的。
分析一下_CFRunLoopGet0做了什么事:

// 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();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&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);
        __CFSpinLock(&loopsLock);
    }
    
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&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
        __CFSpinUnlock(&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;
}

1.判断参数为空,就赋值为主线程;
2.如果__Runloops不存在,定义一个CFMutableDictionaryRef,根据mainThread创建一个mainRunloop,接着通过主线程指针为key,mainRunloop对象为value存入CFMutableDictionaryRef,然后将这个dict数据保存到__Runloops
3.然后再通过GetValue方式从__Runloops以主线程指针为key拿到loop
4.如果拿到的loop为不存在,那再重新创建一个并赋值到响应的value。

结论一:Runloop与Thread是一一对应的

viewDidLoad里面声明一个线程WJThread继承NSThread

WJThread *thread = [[WJThread alloc] initWithBlock:^{
  NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
  [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"hello word");
  }];
}];
thread.name = @"wjCode";
[thread start];
image.png

并没有启动计时器去打印hello word.
然而仅仅需要让名为wjCode线程的Runloop开启,就可以唤醒计时器打印hello word.

WJThread *thread = [[WJThread alloc] initWithBlock:^{
  NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
  [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"hello word");
  }];
  [[NSRunLoop currentRunLoop] run];
}];
thread.name = @"wjCode";
[thread start];

结论二:主线程默认开启Runloop,而子线程默认不开启。

源码学习Runloop结构

NSRunloop其实是由CFRunloop封装的。
CFRunloop和线程是一对一的关系。

Runloop结构

__CFRunLoop:

struct __CFRunLoop {
    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
    pthread_t _pthread;
    uint32_t _winthread; // Windows使用的,不予理会
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    //  下面几个是不常见的,方便底层调用的对象
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

CFRunloop与CFRunLoopMode是一对多的关系。

__CFRunLoopMode:

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

CFRunLoopMode与CFRunloopSource是一对多关系。
CFRunLoopMode与CFRunloopTimer是一对多关系。
CFRunLoopMode与CFRunloopObserver是一对多关系。

CFRunLoop关系图

CFRunloop可以拥有很多个Mode,但是一个周期运行只能依赖于一个Mode。

CFRunloopMode有5种类型:
1.kCFRunLoopDefaultMode
2.UITrackingRunLoopMode
3.UIInitializationRunLoopMode // 系统用的
4.GSEventReceiveRunLoopMode
5.kCFRunLoopCommonModes // 注意这是一种伪Mode,它是一种组合

源码学习CFRunloopTimer

刚才写了一段代码:

WJThread *thread = [[WJThread alloc] initWithBlock:^{
  NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
  [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"hello word");
  }];
  [[NSRunLoop currentRunLoop] run];
}];
thread.name = @"wjCode";
[thread start];

在ViewController的view上面添加一个UIScollView运行后:

当滑动UIScollView的时候,Timer就不会回调去打印了。这是因为滑动时,当前的Runloop切换了Mode。

从Runloop入口开始进来开始核心代码分析:

Runloop入口
CFRunLoopRunSpecific

再进入__CFRunLoopRun函数,发现逻辑非常长。就不截图了,找到__CFRunloopDoBlocks

__CFRunLoopRun

发现从modes里面取出当前一个Mode,这里写了一个死循环遍历item,进一步验证了一个Mode下面有很多的Item

__CFRunLoopDoBlocks-item循环1

如果我们的Timer加入的Mode与当前currentRunloop Mode是对等的;或者 Timer加入到CommonMode与与当前currentRunloop Mode是对等的,那么就返回是否继续执行状态。

__CFRunLoopDoBlocks-item循环2

如果允许继续执行,那就是当前的block执行了:

__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

首先我们的Timer的Mode默认的kCFRunLoopDefaultMode,而滑动UIScollView时的Mode就切换成了UITrackingRunLoopMode,经过上面的条件一对比就不会让它去doit
再者如果我们的Timer加入到kCFRunLoopCommonModes中,上面源码的条件是允许它去doit

所以我们解决上面滑动UI时候,Timer的回调不执行可以有两种方式:

1.让Timer加入到kCFRunLoopCommonModes中
2.另启动一个线程开启RunLoop去执行Timer的任务。(Timer的Mode依旧是kCFRunLoopDefaultMode)

那么addTimer又做了哪儿些操作呢?

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

来看一下源码:
将timer存入_commonModeItems,将这个set加完为止。

addTimer
__CFRunLoopAddItemToCommonModes

else后的操作就是也然是找一下items和添加到items返回。一旦找到了items,那就能继续执行doit。

__CFRunloopRun里会触发__CFRunloopDoTimers

遍历timers拿出每一个timer去执行回调

// mode and rl are locked on entry and exit
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) {    /* DOES CALLOUT */
    Boolean timerHandled = false;
    uint64_t oldFireTSR = 0;
    
    /* Fire a timer */
    CFRetain(rlt);
    __CFRunLoopTimerLock(rlt);
    
    if (__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && !__CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {
        void *context_info = NULL;
        void (*context_release)(const void *) = NULL;
        if (rlt->_context.retain) {
            context_info = (void *)rlt->_context.retain(rlt->_context.info);
            context_release = rlt->_context.release;
        } else {
            context_info = rlt->_context.info;
        }
        Boolean doInvalidate = (0.0 == rlt->_interval);
        __CFRunLoopTimerSetFiring(rlt);
        // Just in case the next timer has exactly the same deadlines as this one, we reset these values so that the arm next timer code can correctly find the next timer in the list and arm the underlying timer.
        rlm->_timerSoftDeadline = UINT64_MAX;
        rlm->_timerHardDeadline = UINT64_MAX;
        __CFRunLoopTimerUnlock(rlt);
        __CFRunLoopTimerFireTSRLock();
        oldFireTSR = rlt->_fireTSR;
        __CFRunLoopTimerFireTSRUnlock();
        
        __CFArmNextTimerInMode(rlm, rl);
        
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
        CHECK_FOR_FORK();
        if (doInvalidate) {
            CFRunLoopTimerInvalidate(rlt);      /* DOES CALLOUT */
        }
        if (context_release) {
            context_release(context_info);
        }
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        __CFRunLoopTimerLock(rlt);
        timerHandled = true;
        __CFRunLoopTimerUnsetFiring(rlt);
    }
    if (__CFIsValid(rlt) && timerHandled) {
        /* This is just a little bit tricky: we want to support calling
         * CFRunLoopTimerSetNextFireDate() from within the callout and
         * honor that new time here if it is a later date, otherwise
         * it is completely ignored. */
        if (oldFireTSR < rlt->_fireTSR) {
            /* Next fire TSR was set, and set to a date after the previous
             * fire date, so we honor it. */
            __CFRunLoopTimerUnlock(rlt);
            // The timer was adjusted and repositioned, during the
            // callout, but if it was still the min timer, it was
            // skipped because it was firing.  Need to redo the
            // min timer calculation in case rlt should now be that
            // timer instead of whatever was chosen.
            __CFArmNextTimerInMode(rlm, rl);
        } else {
            uint64_t nextFireTSR = 0LL;
            uint64_t intervalTSR = 0LL;
            if (rlt->_interval <= 0.0) {
            } else if (TIMER_INTERVAL_LIMIT < rlt->_interval) {
                intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
            } else {
                intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
            }
            if (LLONG_MAX - intervalTSR <= oldFireTSR) {
                nextFireTSR = LLONG_MAX;
            } else {
                uint64_t currentTSR = mach_absolute_time();
                nextFireTSR = oldFireTSR;
                while (nextFireTSR <= currentTSR) {
                    nextFireTSR += intervalTSR;
                }
            }
            CFRunLoopRef rlt_rl = rlt->_runLoop;
            if (rlt_rl) {
                CFRetain(rlt_rl);
                CFIndex cnt = CFSetGetCount(rlt->_rlModes);
                STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
                CFSetGetValues(rlt->_rlModes, (const void **)modes);
                // To avoid A->B, B->A lock ordering issues when coming up
                // towards the run loop from a source, the timer has to be
                // unlocked, which means we have to protect from object
                // invalidation, although that's somewhat expensive.
                for (CFIndex idx = 0; idx < cnt; idx++) {
                    CFRetain(modes[idx]);
                }
                __CFRunLoopTimerUnlock(rlt);
                for (CFIndex idx = 0; idx < cnt; idx++) {
                    CFStringRef name = (CFStringRef)modes[idx];
                    modes[idx] = (CFTypeRef)__CFRunLoopFindMode(rlt_rl, name, false);
                    CFRelease(name);
                }
                __CFRunLoopTimerFireTSRLock();
                rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
                for (CFIndex idx = 0; idx < cnt; idx++) {
                    CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
                    if (rlm) {
                        __CFRepositionTimerInMode(rlm, rlt, true);
                    }
                }
                __CFRunLoopTimerFireTSRUnlock();
                for (CFIndex idx = 0; idx < cnt; idx++) {
                    __CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
                }
                CFRelease(rlt_rl);
            } else {
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopTimerFireTSRLock();
                rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
                __CFRunLoopTimerFireTSRUnlock();
            }
        }
    } else {
        __CFRunLoopTimerUnlock(rlt);
    }
    CFRelease(rlt);
    return timerHandled;
}

__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__就是timer函数回调声明,没别的就是调用。上文有讲解。

总结timer:

  • 1.timer的创建
  • 2.将timer加入到runloop
    • 2.1mode匹配
    • 2.2将timer/context等等存储到集合里
    1. 等待runloop去run起来
    • 3.1 __CFRunloopDoTimers
    • 3.2 遍历拿出timer/context等,去执行回调(__CFRunloopDoTimer)
      (遍历是因为可能一个runloop里有多个timer)
    • 3.3__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

Timer和Runloop总结:

1.Timer一定要加入到响应Runloop的Mode(Timer加到items)。
2.CFRunloopRun时,里面会调用doBlock;里面实际是一个while循环,遍历当前的item->next,判断doIt(是否能执行Mode);遍历完了之后,如果能doIt就是对block的调动,消费掉这个item。
  而我们的Timer回调最终会封装block里面。

源码学习CFRunloopObserver

先看源码

可以看到observer对我们有事件监听和退出监听的回调,中间就是我们的处理事件(处理item的代码逻辑)。

举例代码:
ViewControllerviewDidLoad里面声明一个CFRunLoopObserver:

CFRunLoopObserverContext context = {
        0,
        ((__bridge void *)self),
        NULL,
        NULL,
        NULL
    };
    CFRunLoopRef rlp = CFRunLoopGetCurrent();
    /**
     参数一:用于分配对象的内存
     参数二:你关注的事件
          kCFRunLoopEntry=(1<<0),
          kCFRunLoopBeforeTimers=(1<<1),
          kCFRunLoopBeforeSources=(1<<2),
          kCFRunLoopBeforeWaiting=(1<<5),
          kCFRunLoopAfterWaiting=(1<<6),
          kCFRunLoopExit=(1<<7),
          kCFRunLoopAllActivities=0x0FFFFFFFU
     参数三:CFRunLoopObserver是否循环调用
     参数四:CFRunLoopObserver的优先级 当在Runloop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0
     参数五:回调,比如触发事件,我就会来到这里
     参数六:上下文记录信息
     */
    CFRunLoopObserverRef observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, lgRunLoopObserverCallBack, &context);
    CFRunLoopAddObserver(rlp, observerRef, kCFRunLoopDefaultMode);

设置状态的回调,打印相关信息

void lgRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    NSLog(@"%lu-%@",activity,info);
}

可以看到我们控制台不断在打印:

image.png

Observer与Runloop总结:

不断监听Runloop状态

源码学习CFRunloopSource

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;
};

看到联合体里有着CFRunLoopSourceContextCFRunLoopSourceContext1,其对应的就是Source0Source1

可以看看Runloop工作流程图:

Runloop工作流程

Source0:

只包含一个`回调函数指针`,限制是要在程序里给它一个主动的响应,比如`performSelector`。
怎么响应呢?
1.设置source的signal源,标记这个为待处理的 `CFRunLoopSourceSignal` // 一个执行信号
2.调用wekeUp唤醒Runloop处理事件 `CFRunLoopWakeUp` // 防止沉睡状态

写一个source0的demo:

// 就是喜欢玩一下: 我们下面来自定义一个source
- (void)source0Demo{
    CFRunLoopSourceContext context = {
        0,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        schedule,
        cancel,
        perform,
    };
    /** 
     参数一:传递NULL或kCFAllocatorDefault以使用当前默认分配器。
     参数二:优先级索引,指示处理运行循环源的顺序。这里我传0为了的就是自主回调
     参数三:为运行循环源保存上下文信息的结构
     */
    CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
    CFRunLoopRef rlp = CFRunLoopGetCurrent();
    // source --> runloop 指定了mode  那么此时我们source就进入待绪状态
    CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode);
    // 一个执行信号
    CFRunLoopSourceSignal(source0);
    // 唤醒 run loop 防止沉睡状态
    CFRunLoopWakeUp(rlp);
    // 取消 移除
//    CFRunLoopRemoveSource(rlp, source0, kCFRunLoopDefaultMode);
    
    CFRelease(rlp);
}
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
    NSLog(@"准备了昂,准备啦");
}

void perform(void *info){
    NSLog(@"执行了昂,执行啦");
}

void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
    NSLog(@"取消了昂,取消啦");
}

执行结果:(如果把remove代码打开注释,就会打印 “取消了昂,取消啦”)

执行结果

那么什么样的事件才是Source0呢?
处理app内部事件 / app自己负责管理的事务 (UIEvent/CFSocket),比如系统捕获到的触摸事件、自己发送的响应事件等等很多。Source1相对起来很少,Source1主要依赖于port。

Source1:包含有mach_port回调函数指针
Source1主要基于port进行线程之间的通讯,几乎很少用到,用起来太麻烦了。

那么port又是什么呢?
官方文档有介绍:作用于线程之间或任务之间的port与port之间的充当通讯桥梁。

// 先声明两个port属性
@property (nonatomic, strong) NSPort* subThreadPort; // 子线程port
@property (nonatomic, strong) NSPort* mainThreadPort; // 主线程port

- (void)setupPort{
    [self setupMainPort];
    [self setupSubPort];
}

- (void)setupMainPort {
    self.mainThreadPort = [NSPort port];
    self.mainThreadPort.delegate = self;
    // port - source1 -- runloop
    [[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];
}

- (void)setupSubPort {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        self.subThreadPort = [NSPort port];
        self.subThreadPort.delegate = self;
        [[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run]; // 子线程的runloop必须手动开启
    }];
    [thread start];
}

// 点击触摸事件的时候触发,
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSMutableArray* components = [NSMutableArray array];
    NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
    [components addObject:data];
    // 子线程port发送一个数据消息
    [self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}

// NSPortDelegate
- (void)handlePortMessage:(id)message {
    NSLog(@"%@", [NSThread currentThread]); // 8 1
}

运行点击屏幕输出内容:

source1结果

当前发送消息的是子线程,port代理捕获到。并且还捕获到了信息:

- (void)handlePortMessage:(id)message {
    NSLog(@"%@", [NSThread currentThread]); // 8 1
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([message class], &count);
    for (int i = 0; i

了解port的工作后,回来看看我们原本代码里写的线程通讯代码:

- (void) task {
    // 主线 -- 子线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@", [NSThread currentThread]); // 8
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"%@", [NSThread currentThread]); // 1
        });
    });
}

对比以上两种线程之间的通讯会发现我们会更偏向于第二种线程通讯的方式,Source1会显得代码更麻烦些但是更加底层。

Runloop流程

Runloop流程

在RunLoop启动之后会发送一个通知,来告知观察者
将要处理Timer/Source0事件这样一个通知的发送
处理Source0事件
如果有Source1要处理,这时会通过一个go to语句的实现来进行代码逻辑的跳转,处理唤醒是收到的消息
如果没有Source1要处理,线程就将要休眠,同时发送一个通知,告诉观察者
然后线程进入一个用户态到内核态的切换,休眠,然后等待唤醒,唤醒的条件大约包括三种:
1、Source1
2、Timer事件
3、外部手动唤醒
线程刚被唤醒之后也要发送一个通知告诉观察者,然后处理唤醒时收到的消息
回到将要处理Timer/Source0事件这样一个通知的发送
然后再次进行上面步骤,这就是一个RunLoop的事件循环机制

内部代码逻辑整理如下:

/// 用DefaultMode启动
void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
 
/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
 
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
    
    /// 首先根据modeName找到对应mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
    /// 如果mode里没有source/timer/observer, 直接返回。
    if (__CFRunLoopModeIsEmpty(currentMode)) return;
    
    /// 1. 通知 Observers: RunLoop 即将进入 loop。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
    
    /// 内部函数,进入loop
    __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
        
        Boolean sourceHandledThisLoop = NO;
        int retVal = 0;
        do {
 
            /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
            /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
            /// 执行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);
            
            /// 4. RunLoop 触发 Source0 (非port) 回调。
            sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
            /// 执行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);
 
            /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
            if (__Source0DidDispatchPortLastTime) {
                Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                if (hasMsg) goto handle_msg;
            }
            
            /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
            if (!sourceHandledThisLoop) {
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
            }
            
            /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
            /// • 一个基于 port 的Source 的事件。
            /// • 一个 Timer 到时间了
            /// • RunLoop 自身的超时时间到了
            /// • 被其他什么调用者手动唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
                mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
            }
 
            /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
            
            /// 收到消息,处理消息。
            handle_msg:
 
            /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
            if (msg_is_timer) {
                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
            } 
 
            /// 9.2 如果有dispatch到main_queue的block,执行block。
            else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } 
 
            /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
            else {
                CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
                if (sourceHandledThisLoop) {
                    mach_msg(reply, MACH_SEND_MSG, reply);
                }
            }
            
            /// 执行加入到Loop的block
            __CFRunLoopDoBlocks(runloop, currentMode);
            
 
            if (sourceHandledThisLoop && stopAfterHandle) {
                /// 进入loop时参数说处理完事件就返回。
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout) {
                /// 超出传入参数标记的超时时间了
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(runloop)) {
                /// 被外部调用者强制停止了
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                /// source/timer/observer一个都没有了
                retVal = kCFRunLoopRunFinished;
            }
            
            /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
        } while (retVal == 0);
    }
    
    /// 10. 通知 Observers: RunLoop 即将退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

以上就是Runloop探索知识

你可能感兴趣的:(Runloop工作原理(源码分析))