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唤醒处理Sources19.处理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会在线程结束时销毁