RunLoop

RunLoop是什么

什么是RunLoop,顾名思义,RunLoop就是在‘跑圈’,其本质是一个do while循环。

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
do {
//接受消息->等待->处理
}while(message != quit)

线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

RunLoop有什么

在 CoreFoundation 中关于 RunLoop 有 5 个类:

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

一个Runloop对应一条线程,一个runloop里面可以有多个CFRunLoopModeRef(模式)。

同一个时刻,RunLoop只能是在一个mode上面的运行。如果需要切换mode,只能是退出currentMode,切换到指定的 mode。

每一个mode又可以包含多个 source/timer/observer。不同mode里面的子元素,互不影响。

5个类的对应关系大概是:
RunLoop

RunLoopMode

一个 run loop mode 是若干个 source、timer 和 observer 的集合。它能帮我们过滤掉一些不想要的事件。

一个 RunLoop 在某个 mode 下运行时,不会接收和处理其他 mode 的事件。

要保持一个 mode 活着,就必须往里面添加至少一个 source、timer 或 observer 。

NSDefaultRunLoopMode是默认的模式,程序运行的大多时候都处于该 mode 下

UITrackingRunLoopMode是滑动 tableView 或 scrollerView 时为了界面流畅而用的 mode

例如:
NSDefaultRunLoopMode有需要执行的item时,Loop会执行该item
如果UITrackingRunLoopMode有需要执行的item时,Loop会切换到UITrackingRunLoopMode执行相应的item,而NSDefaultRunLoopMode下的item会暂停执行
当UITrackingRunLoopMode的item执行完毕时,Loop会继续执行NSDefaultRunLoopMode的item

CFRunLoop里面有一个伪mode叫做 kCFRunLoopCommonModes,它不是一个真正的 mode,而是若干个 mode 的集合。
只要加入到了 CommonModes 里面,就相当于添加到了它里面所有的 mode 中。

RunLoopSource

source 是事件产生的地方(输入源),虽然官方文档在概念上把 source 分为三类:Port-Based Sources,Custom Input Sources,Cocoa Perform Selector Sources。

但在源码中 source 只有两个版本:source0 和 source1,它们的区别在于它们是怎么被标记 (signal) 的。

source0 是app内部的消息机制,使用时需要调用 CFRunLoopSourceSignal()来把这个 source 标记为待处理,然后掉用 CFRunLoopWakeUp() 来唤醒 RunLoop,让其处理这个事件。

source1 是基于 mach_ports 的,用于通过内核和其他线程互相发送消息。

iOS / OSX 都是基于 Mach 内核,Mach 的对象间的通信是通过消息在两个端口(port)之间传递来完成。

很多时候我们的 app 都是处于什么事都不干的状态,在空闲前指定用于唤醒的 mach port 端口,然后在空闲时被 mach_msg() 函数阻塞着并监听唤醒端口,mach_msg() 又会调用 mach_msg_trap() 函数从用户态切换到内核态,这样系统内核就将这个线程挂起,一直停留在 mach_msg_trap 状态。直到另一个线程向内核发送这个端口的 msg 后,trap 状态被唤醒,RunLoop 继续开始干活。

其实,总结下来,事件产生的地方就是source(输入源),运用发消息的机制,让事件可以唤醒休眠的RunLoop执行。

示例代码:

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.source, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(self.source, ^{
    NSLog(@"%@", [NSThread currentThread]);
});
dispatch_resume(self.source);

RunLoopTimer

这里的 timer 看起来名字是用C语言的样式,其实是完全等价于我们在OC里面的计时器NSTimer。

所以,在平时编程的过程中,我们最开始意识到有 runloop 这回事,就是了使用 NSTimer 的时候。这一点在后面的具体场景应用会详细提到的。

另外,这个 CFRunLoopTimerRef,还可以由方法 performSelector:afterDelay:来触发。(因为,本质上 afterDelay, 底层就是启动了 timer ,不然怎么检测具体时间,然后调用回调方法呢。)

示例代码:

NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- (void)addTimer {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }];
    [thread start];
}

- (void)doSomething {
    NSLog(@"do something");
    
    if (_finished) {
        [NSThread exit];
    }
}

RunLoopObserver

主要用途就是监听 RunLoop 的状态变化。

它可以监听RunLoop的7种状态:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
	// 即将进入 loop
	kCFRunLoopEntry = (1UL << 0),
	// 即将处理 timer
	kCFRunLoopBeforeTimers = (1UL << 1),
	// 即将处理 source
	kCFRunLoopBeforeSources = (1UL << 2),
	// 即将 sleep
	kCFRunLoopBeforeWaiting = (1UL << 5),
	// 刚被唤醒,退出 sleep
	kCFRunLoopAfterWaiting = (1UL << 6),
	// 即将退出
	kCFRunLoopExit = (1UL << 7),
	// 全部的活动
	kCFRunLoopAllActivities = 0x0FFFFFFFU
};

示例代码:

static void observerCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    UIViewController *controller = (__bridge UIViewController *)info;
}

- (void)addRunLoopObserver {
    CFRunLoopObserverContext context = {
        0,
        (__bridge void *)self,
        &CFRetain,
        &CFRelease,
        NULL
    };

    static CFRunLoopObserverRef observerRef;
    observerRef = CFRunLoopObserverCreate(NULL, kCFRunLoopAfterWaiting, YES, 0, observerCallBack, &context);
    
    CFRunLoopRef runLoopRef = CFRunLoopGetCurrent();
    CFRunLoopAddObserver(runLoopRef, observerRef, kCFRunLoopCommonModes);
    
    CFRelease(observerRef);
}

RunLoop如何运行

1. 获取RunLoop

要获取主线程或当前线程对应的 RunLoop,只能通过 CFRunLoopGetMain 或 CFRunLoopGetCurrent 函数。

runloop 和 pthread_t (也就是线程)是一一对应的。

这样一一对应的关系是保存在一个全局的 dictionary 中的。

内部产生 runloop 的机制,与我们 iOS 开发中常用的懒加载很相似。

只有到了第一次要使用的时候,才会创建。当线程销毁的时候,也销毁相应的 runloop。

2. runloop 运行逻辑

runloop 整个的运行逻辑都是在于三个重要的对象如何运作:source (输入源)、timer (定时器)、observer (观察者)。

上面的关于 runloop 的相关类里面有过介绍,observer 时刻监听,整个 runloop 的7种 状态的变化。

在上面 7 种状态里面,对应着不同的处理。
RunLoop

RunLoop和线程之间的关系

虽然RunLoop与Thread的关系十分密切,但是并不是每个Thread都拥有一个RunLoop的

在iOS中,除了系统会为主线程自动创建一个RunLoop,在子线程中,我们需要手动获取线程对应的RunLoop:

[NSRunLoop mainRunLoop]
[NSRunLoop currentRunLoop]

  1. RunLoop和Thread是一一对应的(key: pthread value:runLoop)
  2. Thread默认是没有对应的RunLoop的,仅当主动调用Get方法时,才会创建
  3. 所有Thread线程对应的RunLoop被存储在全局的__CFRunLoops字典中。同时,主线程在static CFRunLoopRef __main,子线程在TSD中,也存储了线程对应的RunLoop,用于快速查找

这里有一点要弄清,Thread和RunLoop不是包含关系,而是平等的对应关系。Thread的若干功能,是通过RunLoop实现的

另一点是,RunLoop自己是不会Run的,需要我们手动调用Run方法(Main RunLoop会由系统启动),我们的RunLoop才会跑圈。静止(注意,这里的静止不是休眠的意思)的RunLoop是不会做任何事情的

示例代码:

- (void)multithread {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"step 1 %@", [NSThread currentThread]);
        [self performSelector:@selector(doSomething) withObject:self afterDelay:3];
        NSLog(@"step 2 %@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"step 3 %@", [NSThread currentThread]);
    });
}

- (void)doSomething {
    NSLog(@"do something %@", [NSThread currentThread]);
}

运行结果:

step 1 <NSThread: 0x6000027cf480>{number = 3, name = (null)}
step 2 <NSThread: 0x6000027cf480>{number = 3, name = (null)}
do something <NSThread: 0x6000027cf480>{number = 3, name = (null)}
step 3 <NSThread: 0x6000027cf480>{number = 3, name = (null)}

从中可以看出:

  • 每个线程都有一个RunLoop,默认为休眠状态,需手动唤醒
  • [NSRunLoop run]唤醒RunLoop,并使线程无法继续执行
  • timer结束后,RunLoop进入休眠状态,线程继续执行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值