RunLoop底层原理

2020-03-17  本文已影响0人  85ca4232089b

CFRunloop的源码密码: 95pj
官方解释:
Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

Run loop management is not entirely automatic. You must still design your thread’s code to start the run loop at appropriate times and respond to incoming events. Both Cocoa and Core Foundation provide run loop objects to help you configure and manage your thread’s run loop. Your application does not need to create these objects explicitly; each thread, including the application’s main thread, has an associated run loop object. Only secondary threads need to run their run loop explicitly, however. The app frameworks automatically set up and run the run loop on the main thread as part of the application startup process.
runloop是一个事件处理循环,可以使用它来调度工作并协调传入事件的接收。run循环的目的是在有工作要做时让线程保持忙碌,在没有工作时让线程休眠.
Core Foundation和Foundation库都提供相应的runloop功能。每个线程,包括应用程序的主线程,都有一个关联的run loop对象。主线程的runloop是默认启动的,子线程runloop默认是不启动的,需要手动启动。

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

runloop runloop是由线程创建,是一个do while循环。其实runloop是一个对象,提供一个入口函数,程序进入之后,会进行do...while循环,保证应用程序的运行,不被退出。

runloop的作用

• 保持程序的持续运行
• 处理 app的各种事件(触摸、定时器、performSelector)
• 节省CPU资源,优化程序的性能:该做事就做事,该休息就休息

// 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);

在main函数中:
• 第一步会在info.plist文件中查找,如果没有
• 就会在UIApplication 的delegate中查找
所以通过启动的堆栈信息分析会发现当主程序已启动,主线程的runloop就已经启动了

线程&runloop.png
先是libdyld.dylib`start函数->UIApplicationMain函数->CoreFoundation框架的runloop
我们知道UIApplicationMain是返回一个int类型,但是如果直接return 0|nil的话,程序已启动就会退出
使用场景:NSTimer 、performSelector、GCD 、block
CFRUNLOOP的类型大概包括:
常见的runloop的类型
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE 主队列
runloop使用场景.png

runloop和线程的关系

runloop的官方文档
可以发现runloop其实是在Threading Programming Guide下面的一个分支,所以说明了runloop和线程是通过key-value一一对应的 .
runloop 是一种消息机制处理模式
上面讲到main函数是一个do...while循环,但是并不是一个死循环

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

会发现程序的内存的增长


runloop内存.gif
CF_EXPORT pthread_t _CF_pthread_main_thread_np(void);
#define pthread_main_thread_np() _CF_pthread_main_thread_np()

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

_CFRunLoopGet0一个全局可变的CFMutableDictionaryRef记录
• runloop的创建:CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
• runloop的存储:CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);//pthreadPointer的指针为value,mainLoop为key
• runloop的获取:CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
小的例子

    /**
     * 通过外部看量控制线程的退出,线程退出导致runloop也停止了,timer 是依赖于runloop的runloop停止 timer也停止了
     */
    self.isStopping = NO;
    customerThread *thread = [[customerThread alloc] initWithBlock:^{
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"hello word");            // 退出线程--结果runloop也停止了
            if (self.isStopping) {
                [NSThread exit];
            }
        }];
        [[NSRunLoop currentRunLoop] run];//子线程的runloop默认是不开启的
    }];
    
    thread.name = @"customerRunloop";
    [thread start];//线程创建完成之后一定要start

源码分析:对象和model

CFRunloopSourceRef&CFRunLoopObserverRef&CFRunLoopObserverTimer

• CFRunLoopRef
CFRunLoopRef lp = CFRunLoopGetCurrent();
CFRunLoopRef是__CFRunLoop的结构体

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
   //删除了一些不常见的信息,不要介意哈
};

• CFRunLoopModeRef
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(lp);
CFRunLoopMode是__CFRunLoopMode的结构体

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
//上面四个统称为items

一个 model可以包含多个items
一个runloop可以拥有多个model,但是同时只能在一个模式下运行


runloop.jpeg

• CFRunLoopSourceRef

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;
}
union联合体/共用体

• union中可以定义多个成员,union的大小由最大的成员的大小决定。
• union成员共享同一块大小的内存,一次只能使用其中的一个成员。
• 对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节)联合体
• union的存放顺序是所有成员都从低地址开始存放的。

source0

• app的内部事件: 触摸事件等等|UIEvent | Socket
• 只包含一个函数指针 :不能主动唤醒,给他一个响应(signal)标记为待处理,weakup唤醒runloop处理事件

source1

• 包含了一个mach_port&一个回调函数指针
• CFRunLoopTimerRef

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

timer是如何假造底层runloop中的?

timer加入到runloop中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {

    if (modeName == kCFRunLoopCommonModes) {
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        CFSetAddValue(rl->_commonModeItems, rlt);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            // timer -- items()
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {
    }
}
  1. 判断当前timer的模式==kCFRunLoopCommonModes
  2. CFSetAddValue(rl->_commonModeItems, rlt);把当前的timer的model添加到_commonModeItems集合中
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
        CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
        CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
        CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
    }
}

会加入各种事物:source 、timer、observer

runloop执行timer
• CFRunloopRun一个do...while循环,调用CFRunLoopRunSpecific

    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

• CFRunLoopRunSpecific通过调用__CFRunLoopRun,逻辑很长,有一个核心的函数

            __CFRunLoopDoBlocks(rl, rlm);//rl  是runloop。rlm是runloop Model

当上面添加的items有值的时候就会执行下面的while循环

while (item) {
        if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
            doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } else {
            doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        }
          if (!doit) prev = curr;
        if (doit) {
            if (prev) prev->_next = item;
            if (curr == head) head = item;
            if (curr == tail) tail = prev;
            void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
            if (doit) {
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
                did = true;
            }
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
        }
}
  1. curr->_mode 是当前runloop运行时的model。 curMode是timer添加时的model
    如果timer 加入的mode 和 我们现在runloop的mode 相等 || curr->_mode = kCFRunLoopCommonModes 相等
  2. 如果doit 存在,则执行CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK(block);函数 事务就能执行
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) {
    if (block) {
        block();//执行block
    }
    asm __volatile__(""); // thwart tail-call optimization
}

所以这就解释了 ,timer的工作原理
• timer 必须要添加到runloop的一个model下 timer添加到items中
• runloop。执行__CFRunLoopDoBlocks。while循环当前的item item->一层一层向下指,判断doit,直到items中没有 ,最后执行block。
• timer添加的时候 timer是处于RunLoopDefaultMode模式下
• __CFRunLoopRun判断,doit是存在的,所以timer可以运行
• 但是一旦滑动的时候timer的模式就切换到了UITrackingRunLoopMode,所以doit是不存在的所以是不会继续执行的timer也就停止了

runloop结构.png

自定义实现timer


customer_timer.png

• CFRunLoopObserverRef

runloop原理分析

首先runloop是一个do...while循环
• 根据添加runloop的时候的modeName找到本次运行的mode,判断如果没找到 || mode中没有注册任何事件,则就此停止,不进入循环。

  1. kCFRunLoopEntry 通知 Observers: RunLoop 即将进入 loop __CFRunLoopDoObservers 判断端口还是否一致、延迟性等等。dispatch_source
  2. 外层的do...while循环: 通知Observers: RunLoop 即将触发 Timer 回调。
  3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。__CFRunLoopDoObservers
  4. RunLoop 触发 Source0 (非port) 回调。__CFRunLoopDoSources0
  5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。如果接收到了消息的话,前往第9步开始处理msg
  6. 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
  7. 内层的do...while循环 :接收waitSet端口的消息 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop
  8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
  9. 事件触发,处理唤醒时收到的消息,之后回调方法2
    9.1 如果一个 Timer 到时间了,触发这个Timer的回调。__CFRunLoopDoTimers
    9.2 果有dispatch到main_queue的block,执行block
    9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
  10. kCFRunLoopExit 通知 Observers: RunLoop 即将退出。
        /// 执行加入到Loop的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            /// 进入loop时参数说处理完事件就返回。
            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)) {
            /// source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished;
        }
        /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
    } while (0 == retVal);
runloop原理.png
上一篇 下一篇

猜你喜欢

热点阅读