什么是runloop?
个人理解为:他是一个运行循环,因为他的存在才会导致我们的运行的程序才不会挂掉,我们的事件处理、定时器等等的事件才能有所反应。
举个例子:
看下如下的代码:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
可以看到命令行的main是没有runloop的,但是我们的ios程序是有runloop的,也就是我们的命令行函数执行完了 直接就return0了 那么程序也就死了 ,而我们的ios程序会一直存在。
所以我们猜测一下UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));的底层实现大致是这样的:
int vel = 0;
do{
// 消息监听
// 消息处理
}while (0 == vel);
runloop的对象获取
NSRunLoop *mainRun = [NSRunLoop mainRunLoop];
NSRunLoop *currentRun = [NSRunLoop currentRunLoop];
CFRunLoopRef mainRunRef = CFRunLoopGetMain();
CFRunLoopRef currentRunRef = CFRunLoopGetCurrent();
NSLog(@"%p %p ==== %p %p",mainRun,currentRun,mainRunRef,currentRunRef);
我们可以看到mainrunloop和currentloop是一样的 但是c语言的runloop和我们的oc的runloop是不一样的,其实一个线程只对应一个runloop,也就是说目前四个都是一样的 下面我证明一下
由此可见他们都是同一个runloop
runloop与线程的关系
1.一个线程与之仅有一个runloop与之对应
2.runloop保存在一个全局的dictory里,线程作为key,value作为runloop
3.线程刚刚开始创建的时候并没有runloop,在第一次获取的时候runloop才开始创建。所以说主线程默认也是没有runloop的 只不过我们开始调用main函数。在main函数中调用了UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));所以他内部是有获取的 所以主线程是存在runloop的。
4.runloop会在线程结束的时候进行销毁,子线程默认是没有开启runloop的。
runloop相关的类 、结构
我们到这个网站(https://opensource.apple.com/tarballs/CF)下载一下底层的源码,打开新建一个工程将我们下载的资料拖进去
1.在coreFoundation中与runloop相关的五个类分别为:
CFRunLoopSourceRef CFRunLoopModeRef CFRunLoopRef CFRunLoopObserverRef CFRunLoopTimerRef
2.runloop 的结构如下:
typedef struct __CFRunLoop *CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
}
其中他的mode结构如下:
typedef struct __CFRunLoopode *CFRunLoopModeRef;
struct __CFRunLoopode{
CFStringRef _name;
CFMutableSetRef _source0;
CGMutablePathRef _source1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
其中modes包含了很多的CFRunLoopModeRef,只不过其中的一个mode是_currentMode
解释mode中的每一个的作用
_name:名字
_source0:触摸事件处理、performSeletor:onThread
_source1: 事件的捕捉、或者线程之间的port通信
_timers: 定时器、[self performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#>]
_observers:各种状态。
下面我们来验证一下_source0,打开函数的调用栈
可以看到source0 确实是触摸事件的处理。
下面在看一下观察的状态
// 创建观察对象
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: // 即将进入loop
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeSources: // 即将处理source
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeTimers: // 即将处理timer
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeWaiting: // 即将进入睡眠
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting: // 刚从睡眠中唤醒
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit: // 退出loop
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
});
// kCFRunLoopCommonModes == kCFRunLoopDefaultMode(没有滚动事件的处理) + UITrackingRunLoopMode(滚动时候的情形)
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
可以看到runloop在点击事件之前是结束等待,然后在继续监听 也就是我们没有做任何事情之前他是处于睡眠状态的。
-
runloop是开始创建的时候就对应一种mode,其中他的常用mode只有kCFRunLoopDefaultMode(没有滚动事件的处理) 和 UITrackingRunLoopMode(滚动时候的情形),他只能结束一种mode在进入另一种mode,而且每个mode分别管理自己的CFRunLoopModeRef
下面来验证一下,
我们在xib中增加一个uitextview 滚动来看和停止滚动的
代码如下:
// 创建观察对象
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(@"kCFRunLoopExit %@",mode);
CFRelease(mode);
break;
}
default:
{
break;
}
}
});
// kCFRunLoopCommonModes == kCFRunLoopDefaultMode(没有滚动事件的处理) + UITrackingRunLoopMode(滚动时候的情形)
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
可以看到mode切换了 在滚动停止的时候。
- 总结:
1.如果当前的mode中没有timer、source0、source1、observer等等的 runloop会立刻退出。
2.runloop创建的时候默认设置一个mode
3.runloop只能从当前的mode退出切换到另一个mode
runloop的执行过程
1.通知observer:进入loop
2.通知observer:即将处理timer
3.通知observer:即将处理sources
4.处理blocks
比如这种情况
CFRunLoopPerformBlock(<#CFRunLoopRef rl#>, <#CFTypeRef mode#>, <#^(void)block#>)
5.处理source0(我们都知道source0是对事件的处理)
6.如果存在source1,那么就会进行第8步
7.通知observer:进行休眠
8.通知observer:结束休眠(被某个消息唤醒)
8.1 处理timers
8.2 处理gcd相关的可能
8.3 处理source1
9.处理blocks
10.根据执行结果决定如何的操作
{
回到第二步骤还是退出loop
}
11.通知observer:退出loop
runloop的运用:
- 1.线程保活
具体说一下他的作用:
有时候我们需要在一个自线程中做事情,但是我们都知道一般来说子线程做完事情之后就死掉,如果我们想在一个子线程中一直做一个事情,让我们来控制自线程的生命周期我们普通的方法是实现不了的。
我们需要用到runloop的知识,让我们的子线程一直存在,直到我们想让他销毁他就销毁。
代码如下:
#import "ViewController.h"
#import "DGThread.h"
@interface ViewController ()
@property (strong, nonatomic) DGThread *thread;
@property (assign, nonatomic) BOOL isStopThread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self)weakSelf = self;
self.thread = [[DGThread alloc] initWithBlock:^{
// 这里面进行线程包活
NSLog(@"当前的线程 ---- %@",[NSThread currentThread]);
// 往runloop里面添加观察者、observer、timer等等
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 开启runloop 让他来进行监听,好处理事件
while (weakSelf.thread && !weakSelf.isStopThread) {
NSLog(@"weakSelf.isStopThread --- %d",weakSelf.isStopThread);
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
// 线程开启了
[self.thread start];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 这个参数穿点为NO,为立刻执行后面的事情,比如立刻执行 123
if (!self.thread) return;
[self performSelector:@selector(startAction) onThread:self.thread withObject:nil waitUntilDone:NO];
NSLog(@"123");
}
- (void)startAction{
NSLog(@"老夫在这里做了很多的事情");
}
- (void)stop{
// 停止runloop
NSLog(@"%s",__func__);
CFRunLoopStop(CFRunLoopGetCurrent());
self.isStopThread = YES;
NSLog(@"已经停止定时器了");
}
-(void)dealloc{
[self stopThread];
NSLog(@"%s",__func__);
}
- (IBAction)stopThread {
if (!self.thread || self.isStopThread) return;
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
NSLog(@"已经停止线程了");
}
@end
- 2防止定时器失效
我们直到我们创建的定时器如果指定的模式是NSDefaultRunloopMode的情况下,那么如果我们空间中有一个UITextView在那滚动,那么定时器就不走了,如果停止滚动就走了。比如:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"asdasdasdasdasd");
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
值得说明上面的这种写法和这种写法是等价的
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
}];
如果我们将这个模式修改为:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"asdasdasdasdasd");
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
我们发现定时器 即使是在滚动的情况下还是能走,当然静止的时候也还是能走的。
3.未完待续,我会继续更新 我觉得runloop的应用。