Runloop:原理篇

作者: 码小菜 | 来源:发表于2020-03-10 19:01 被阅读0次
夜景

目录
一,基本知识
二,Runloop对象
三,底层结构
四,Mode
五,运行流程
六,休眠的原理

一,基本知识

1,含义

运行循环,在程序运行过程中循环的处理事件

2,作用

  • 保证程序能够持续的运行

1>没有runloop

// 执行完return,程序立即退出
int main(int argc, char * argv[]) {
   @autoreleasepool {
       NSLog(@"hello world!");
       return 0;
   }
}

2>有runloop

// 不会执行return,程序不会退出
int main(int argc, char * argv[]) {
   @autoreleasepool {
       int value = 0;
       // 函数内部会启动runloop,runloop会让代码阻塞在此
       value = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
       return value;
   }
}

3>伪代码

int main(int argc, char * argv[]) {
   @autoreleasepool {
       int value = 0;
       do {
           // runloop处理事件或休眠
       } while (1);
       return value;
   }
}
  • 处理程序运行过程中的各种事件

  • 节省资源:有事就处理,没事就休眠

二,Runloop对象

1,CFRunLoopRef源码下载地址

// 获取主线程的runloop对象
CFRunLoopRef main = CFRunLoopGetMain();
// 获取当前线程的runloop对象
CFRunLoopRef current = CFRunLoopGetCurrent();

2,NSRunLoop(对CFRunLoopRef的封装)

// 获取主线程的runloop对象
NSRunLoop *main = [NSRunLoop mainRunLoop];
// 获取当前线程的runloop对象
NSRunLoop *current = [NSRunLoop currentRunLoop];

3,与线程的关系

  • 说明

1>每条线程都有一个runloop对象

2>线程刚创建时并不会创建runloop对象,而是在第一次获取它时创建的

3>主线程的runloop对象会在程序启动时自动创建,子线程的需要手动创建

4>runloop对象会在线程销毁时自动销毁

5>runloop对象存储在一个全局的字典里,线程作为keyrunloop对象作为value

  • 源码
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL;
    // 调用_CFRunLoopGet0
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np());
    return __main;
}

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

// 简化代码
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    // 用线程从字典中取出runloop对象
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    // 如果字典中没有该线程对应的runloop对象
    if (!loop) {
        // 创建runloop对象
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            // 以线程作为key把创建的runloop对象存储到字典中
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        __CFUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    return loop;
}
三,底层结构

1,CFRunLoopRef

typedef struct __CFRunLoop * CFRunLoopRef;

// 简化代码
struct __CFRunLoop {
    pthread_t _pthread;               // 线程对象
    CFMutableSetRef _commonModes;     // 用kCFRunLoopCommonModes标记的mode名称
    CFMutableSetRef _commonModeItems; // 所有添加在kCFRunLoopCommonModes下的事件
    CFRunLoopModeRef _currentMode;    // 当前mode
    CFMutableSetRef _modes;           // 所有mode
};

2,CFRunLoopModeRef

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

// 简化代码
struct __CFRunLoopMode {
    CFStringRef _name;            // 名称
    CFMutableSetRef _sources0;    // CFRunLoopSourceRef集合
    CFMutableSetRef _sources1;    // CFRunLoopSourceRef集合
    CFMutableArrayRef _observers; // CFRunLoopObserverRef数组
    CFMutableArrayRef _timers;    // CFRunLoopTimerRef数组
};

3,关系图

关系图

4,说明

  • 一个runloop包含若干个mode,每个mode又包含若干个source0/source1/observer/timer

  • mode代表runloop的运行模式,runloop每次只能选择一个mode来运行

  • 如果需要切换moderunloop会先退出,然后选择一个mode重新进入

  • 如果mode是空的,runloop会立即退出

四,Mode

1,作用

将不同类型的source0/source1/observer/timer分隔开,这样runloop在某种mode下运行时,只需要处理一种类型的事件,效率会比较高

2,类型

  • kCFRunLoopDefaultMode:默认mode,主线程一般都在这个mode下运行

  • UITrackingRunLoopMode:跟踪界面滑动的mode,保证界面滑动时不受其他mode的影响

  • UIInitializationRunLoopMode:程序启动时会先进入这个mode,启动完成后会切换到kCFRunLoopDefaultMode,此mode便不再使用

  • GSEventReceiveRunLoopMode:接收系统事件的mode,一般用不到

  • kCFRunLoopCommonModes:不是一个真正的mode,只是用来标记
    kCFRunLoopDefaultModeUITrackingRunLoopMode

3,CFRunLoopSourceRef(事件源)

  • source0:非基于port,用户触发的事件,需要手动唤醒线程
source0
  • source1:基于port,其他线程发送的消息,能主动唤醒线程

4,CFRunLoopObserverRef(观察者)

// 监听runloop的运行状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"进入runloop");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"即将处理timers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"即将处理sources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"即将休眠");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"刚被唤醒");
            break;
        case kCFRunLoopExit:
            NSLog(@"退出runloop");
            break;
        default:
            break;
    }
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);

// 打印
即将处理timers
即将处理sources
即将处理timers
即将处理sources
即将休眠
刚被唤醒
即将处理timers
即将处理sources
即将休眠
刚被唤醒
即将处理timers
即将处理sources
即将休眠

5,CFRunLoopTimerRef(定时器)

timer

6,切换mode

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"进入runloop---%@", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
                break;
            case kCFRunLoopExit:
                NSLog(@"退出runloop---%@", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
                break;
            default:
                break;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);
}

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    NSLog(@"%s", __func__);
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
                  willDecelerate:(BOOL)decelerate {
    NSLog(@"%s", __func__);
}

// 打印
-[ViewController scrollViewWillBeginDragging:]
退出runloop---kCFRunLoopDefaultMode 
进入runloop---UITrackingRunLoopMode
-[ViewController scrollViewDidEndDragging:willDecelerate:]
退出runloop---UITrackingRunLoopMode
进入runloop---kCFRunLoopDefaultMode
五,运行流程

1,官方图解

官方

2,自定义图解

自定义

3,源码

  • 入口
入口
  • CFRunLoopRunSpecific
// 简化代码
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    // 通知observers:进入runloop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // 循环处理事件
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知observers:退出runloop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}
  • __CFRunLoopRun
// 简化代码
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:即将处理sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 处理blocks
        __CFRunLoopDoBlocks(rl, rlm);
        // 处理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 可能会再次处理blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        // 是否存在sources1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        
        // 通知observers:即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        // 等待消息唤醒(内部会调用mach_msg函数)
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        // 通知observers:刚被唤醒
        __CFRunLoopUnsetSleeping(rl);
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:
        if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 被timer唤醒
            // 处理timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        } else if (livePort == dispatchPort) { // 被GCD唤醒
            // 处理GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { // 被source1唤醒
            // 处理sources1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }
         
        // 处理blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 是否继续处理事件
        if (sourceHandledThisLoop && stopAfterHandle) { // 否
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) { // 否
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) { // 否
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { // 否
            retVal = kCFRunLoopRunFinished;
        }
    } while (0 == retVal);
    
    return retVal;
}
六,休眠的原理

1,说明

  • 当没有消息时,线程会从用户态切换到内核态,开始休眠等待

  • 当有消息时,线程会从内核态切换到用户态,开始处理事件

  • 监听消息和转换状态都是由mach_msg函数完成的

2,图解

休眠

相关文章

网友评论

    本文标题:Runloop:原理篇

    本文链接:https://www.haomeiwen.com/subject/nqaddhtx.html