RunLoop,顾名思义就是一种iOS框架中的运行循环,而程序在运行中,这个循环一直在为程序做一些事情。
首先大家都知道,我们创建一个项目,在main.m文件的main函数中包含以下内容:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
其实,这个return的内容就是一个RunLoop,其主要目的就是要程序一直进行,不被退出,简单点说就是一个死循环,二在这个循环中,程序可以做自己想做的事,一旦退出循环,程序就结束应用。
除了上述所说,RunLoop可以保持程序的持续运行,它还可以帮我们处理APP运行中的各种事件,比如触摸事件啊,定时器事件等等。而且它还有一个很大的特点,就是在没有事件需要处理的时候,它就会进入休眠,等待下一次被唤醒,在休眠过程中,基本不消耗CPU资源。
在iOS中有Foundation框架中的NSRunLoop和CoreFoundation框架中的CFRunLoop两套关于RunLoop的API可供我们使用,这里我们先简单的来运用下吧:
//当获取到一个RunLoop时,首先它自动会去帮我们创建一个fRunLoop
// OC
NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // 创建一个RunLoop
[runLoop run]; // 运行RunLoop
[NSRunLoop mainRunLoop]; // 获取主线程RunLoop
// C
CFRunLoopGetCurrent(); // 创建一个RunLoop
CFRunLoopRun(); // 运行RunLoop
CFRunLoopGetMain(); // 获取主线程RunLoop
以上是在两个框架下,最简单的RunLoop运用。在上面代码中,我们可以看到,我们可以直接获取一个RunLoop,比方说mainRunLoop,这个方法获取到的是主线程的RunLoop,其实没一个RunLoop都会对应这一条线程,mainRunLoop就对应着我们的主线程,而子线程中也有着与自己对应的RunLoop,就比如如下的代码中:
- (void)viewDidLoad {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadFun) object:nil];
[thread start];
}
- (void)threadFun {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // 创建一个RunLoop
NSLog(@"%p, %p", runLoop, [NSRunLoop mainRunLoop]);
}
打印出来的结果是:
2019-08-29 15:35:10.753941+0800 001-RunLoop使用[68546:25248318] 0x600000244ba0, 0x600000249080
很明显,子线程中创建的RunLoop和主线程的RunLoop不是系统一个,所以每一个线程都对应着一个RunLoop,而且,需要知道的是,RunLoop在线程结束的时候,就会销毁,除非用特定的方法不让线程结束,这个我们后面再讨论。
接下来,我们再来看看和runloop相关的类,这里我用CoreFoundation框架来展示,有以下这几个类:
CFRunLoopMode // RunLoop模式
CFRunLoopRef //RunLoop类
CFRunLoopSourceRef // source(各种事件处理)
CFRunLoopObserverRef //观察者(用于监听RunLoop状态)
CFRunLoopTimerRef //定时器
这里我们看不到底层的源码,所以按照之前的方法,我们去下载源码来看对应的知识点,源码地址:https://opensource.apple.com/tarballs/CF/,下载完之后,打开找到CFRunLoop.c文件,我们先来看CFRunLoopRef这个类型,它对应的是struct __CFRunLoop 这个结构体,这里我们先把结构体内主要的部分抽出来:
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
从抽出来的代码中,我们也可以了解到,每一个RunLoop都对应着一个线程,然后我们再主要来看看CFRunLoopModeRef这个类型,可以找到:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
同样也是一个结构体,抽取之后如下:
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
}
到这里,我们基本上就能看清一个RunLoop的内部结构了,用一张图来总结就是如下:
![](https://img.haomeiwen.com/i10378163/a883e6f25aba8b21.png)
所以,总结得到,RunLoop中包含若干个_commonModes,但是每次只能选择其中一个_currentMode,而每一个_currentMode中又包含若干个Source0/Source1/Timer/Observer
,而且不同组的Source0/Source1/Timer/Observer都是分隔开的,互不影响,当然当mode中没有Source0/Source1/Timer/Observer,这时候RunLoop会退出。
然后我们来看看常见的几种model,一种是kCFRunLoopDefaultMode(NSDefaultRunLoopMode),一种是UITrackingRunLoopMode。
NSDefaultRunLoopMode是默认模式,通常主线程就是在这个模式下。UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
然后我们再看CFRunLoopObserverRef这个监听着RunLoop状态的observer,主要是看看RunLoop中有哪些状态:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入睡眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
这里,我们可以利用在主线程的RunLoop中添加Observer来检测RunLoop在不同模式下的切换,代码如下:
- (void)viewDidLoad {
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
}
在项目中的Main.storyboard中根视图上添加一个textView,然后在程序启动之后进行滚动,你会发现控制台的打印:
2019-08-29 17:41:10.888940+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - kCFRunLoopDefaultMode
2019-08-29 17:41:10.889159+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - UITrackingRunLoopMode
2019-08-29 17:41:12.873182+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - UITrackingRunLoopMode
2019-08-29 17:41:12.873288+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - kCFRunLoopDefaultMode
很明显主线程的RunLoop进行的切换。
由于时间关系,今天暂时分享到这个,之后我会继续分享RunLoop的运行逻辑,以及RunLoop在实际开发中的应用。
网友评论