runloop

202 阅读3分钟

runloop对我我们IOS开发者来说是一个特别重要的部分,我们无时无刻不在隐性或者显性的使用这项技术,本文从以下几个方面来探讨

一:什么是runloop

runloop译为运行循环,本质上是一个对象,你也可以把它理解为一个do while循环,当条件成立时,我们一直不停的在执行循环里的代码

二:如果没有runloop,会发生什么

main函数里的return UIApplicationMain(argc, argv, nil, appDelegateClassName);其实就是用了runloop技术,

如果我们直接return 0会发生什么事呢,整个程序刚启动完就会退出,

三:runloop的底层结构,runloop源码:opensource.apple.com/tarballs/CF…

我们平常用的NSRunloop底层是CFRunLoop,通过底层源码(全局搜索struct __CFRunLoop {),我们可以看到CFRunloop结构体,里面包含了pthread线程,pthread_mutex_t线程锁,_commonModes(重点),currentMode等,接下来我们分下一下_commonModes是什么

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;

    CFMutableSetRef _commonModes;

    CFMutableSetRef _commonModeItems;

    CFRunLoopModeRef _currentMode;

    CFMutableSetRef _modes;

    struct _block_item *_blocks_head;

    struct _block_item *_blocks_tail;

    CFAbsoluteTime _runTime;

    CFAbsoluteTime _sleepTime;

    CFTypeRef _counterpart;

};

_commonModes是一个集合,里面包含了很多的CFRunLoopMode,通过全局搜索struct __CFRunLoopMode {可以看到CFRunLoopMode的结构体,这里我把一些不必要的信息去掉了,我们可以看到一个CFRunLoopMode里包含了很多的sources0,sources1,observers,timers,那么这些又是什么呢

struct __CFRunLoopMode {

    CFRuntimeBase _base;

    pthread_mutex_t _lock; /* must have the run loop locked before locking this */

    CFStringRef _name;

    CFMutableSetRef _sources0;//CFRunLoopSourceRef

    CFMutableSetRef _sources1;

    CFMutableArrayRef _observers;//CFRunLoopObserverRef

    CFMutableArrayRef _timers;//CFRunLoopTimerRef

};

sources0:主要是一些触摸事件,performSelector方法,我们可以通过断点在打印台输入bt指令来验证

sources1:主要是进程间的通信

observers:观察者,用于观察runloop状态的改变

timers:定时器的操作

**四:runloop的执行流程

源码CFRunLoop.c里的CFRunLoopRunSpecific方法**

1.通知Observer,进入Loop

源码:__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)

2.通知Observer,即将处理Timers

源码:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

3.通知Observer,即将处理Sources

源码:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

4.处理blocks

源码:__CFRunLoopDoBlocks(rl, rlm);

5.处理Sources0,有可能再次处理blocks

源码:__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

6.如果存在sources1,跳到第8步

源码:if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {

goto handle_msg;

}

7.通知Observers,即将休眠,开始休眠

源码:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting)//通知oberserver即将休眠

__CFRunLoopSetSleeping(rl);//开始休眠

8.被某个消息唤醒,看是谁唤醒了runloop进行相应的处理

8.1 被timer唤醒处理Timer

8.2 被GCD唤醒处理GCD Async To Main Queue

8.3 被sources1唤醒处理Sources1

9.处理blocks

10.根据执行的结果,决定返回第二步还是退出runloop

五:我们可以使用runloop来做些什么

1.在UIScrollView滑动的时候不让定时器停止

self.timer = [NSTimer timerWithTimeInterval:interval target:self selector: @selector(timer:) userInfo:nil repeats:YES];

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

2.常住线程,有时候我们不希望一个子线程执行完代码后就立即销毁,希望可以常驻在内存中,就可以通过往子线程的runloop不停的添加mode来实现,核心代码:

self.innerThread = [[FtspThread alloc] initWithBlock:^{

// 创建上下文(要初始化一下结构体)

CFRunLoopSourceContext context = {0};

// 创建source

CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);

// 往Runloop中添加source

CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

// 销毁source

CFRelease(source);

// 启动

CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false); }];

[self.innerThread start];

3.当我们的程序崩溃,在执行exit代码前,通过重启runloop来让我们的程序起死回生,参考:github.com/minibear052…

4.监控应用卡顿,参考:github.com/UIControl/L…

六:runloop和线程的关系

对应关系:runloop和线程是一一对应的,RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为Value

开启:线程在第一次获取的时候创建,主线程的runloop会自动开启,子线程的runloop需要手动开启

销毁:runloop会在线程结束时销毁