美文网首页程序员
初次接触 RunLoop

初次接触 RunLoop

作者: yahtzee_ | 来源:发表于2016-03-24 11:43 被阅读286次

最近看了两位大神关于RunLoop的一些资料,对RunLoop算是有了一个初步的认识,在这里整理总结一下自己对于RunLoop的理解。如果我有理解错的地方,希望大家帮我指出。我看的是 @ibireme 的博客和 @我就叫Sunny怎么了 的视频,链接在下面。

深入理解RunLoop

iOS线下分享《RunLoop》by 孙源@sunnyxx

什么是RunLoop


说白了就是一个循环,能够让整个程序一直运行着不会退出,接收用户的信息。当然这个循环是一个十分复杂的循环。

RunLoop与线程的关系


每个线程有一个自己的main函数,当这个main函数执行完后这个线程也就没了。所以我们需要给这个线程创建一个 RunLoop。但是在线程创建的时候并没有 RunLoop ,只有当我们第一次去获取这个线程的 RunLoop 时,才会创建。

RunLoop的结构


每个 RunLoop 中有一个集合其中存放了若干个 Mode,一个 RunLoop 只能在一种 Mode 下运行,如果需要切换那么久要退出 Loop 再重新指定一个 Mode 进入。每个 Mode 中又有若干个 item ,总共有三类,分别是 Source,Timer,Observer。先说简单的

Timer

Timer 就是一个定时器,我们平时使用 NSTimer 与它紧密相关。它包含一个时间长度和一个作为回调的函数指针,当它加入到 RunLoop 后,每次到那个时间点,就会执行一次回调。

Observer

Observer 就是观察者,它也包含一个回调,当 RunLoop 的状态改变时,就会执行这个回调。RunLoop 有以下几种状态:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), 
    kCFRunLoopBeforeTimers  = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2), 
    kCFRunLoopBeforeWaiting = (1UL << 5), 
    kCFRunLoopAfterWaiting  = (1UL << 6), 
    kCFRunLoopExit          = (1UL << 7), 
};

Source

Source就比较难理解了。反正我自己没有弄明白,还是引用一下@ibireme的原话吧。

CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。

Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程

CFRunLoopMode 和 CFRunLoop 的结构

我觉得看了下面这两个结构体后,对于 RunLoop 的理解会明朗很多。

struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array
    ...
};
 
struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
};

__CFRunLoop 中有一个 _modes 用来存放这个 RunLoop 中的所有 Mode。还有一个 _currentMode 用来标记当前的 Mode。而在 _CFRunLoopMode 中除了 _name 就是那三种 item,应该很容易看出来。

需要一提的是在 __CGRunLoop 中有 CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; 这个两个结构。我们可以把一个 Mode 标记为 Common。 这样每次 RunLoop 内容改变时都会把 _commonModeItems 里的 items 全部同步到标记了 Common 的 Mode 中。在后面讲 timer 的例子时我们还会再提到。

RunLoop 的内部逻辑

还是引用一下@ibireme的图片,十分的直观明了。

RunLoop 应用


NSTimer

在我刚刚学 iOS 的时候,一次偶然知道了 NSTimer 这个东西后,高兴的用它模仿了一个系统的计时器。源码在一次整理的时候被我删了。。只能看看截图了:

本来挺开心的,结果一滚动下面的表格,WHAT?!计时器怎么不动了??

后来也查了解决办法,不过一直没搞懂为什么。现在知道了 RunLoop 后总算是明白了。

在主线程中有预置两个 Mode,一个是 Default,还有一个就是 UITrackingRunLoopMode,并且这两个Mode都标记了 Common。这个 Mode 在 ScrollView 滚动时会切换,用来保证滚动的流畅。而我们注册 timer 使用的方法:

 _timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];

会默认把这个 timer 注册到当前 RunLoop 的 DefaultMode 中,那么滚动的时候 Mode 一切换 timer 很显然就没法工作了。解决办法也很容易,可以分为两种:

  • 把timer注册到UITrackingRunLoopMode或者NSRunLoopCommonModes(添加到 CommonModes 中就相当于自动添加到了UITrackingRunLoopMode)。
// 二选一
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
  • 把timer添加到子线程的 RunLoop 中,不管是什么Mode都不会影响到主线程
dispatch_queue_t queue = dispatch_queue_create("com.cyrusdev.queue", DISPATCH_QUEUE_CONCURRENT);
   dispatch_async(queue, ^{
       _timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
       [[NSRunLoop currentRunLoop] run]; // 一定要在注册timer之后让 RunLoop 跑起来
   });

我写了一个Demo,地址在这里,可以感受一下:

RunLoopDemo

AutoreleasePool

在 App 启动后,系统会在主线程的 RunLoop 里注册两个 Observer。

第一个 Observer 监听 kCFRunLoopEntry 也就是即将进入 RunLoop 的状态,在这里会创建一个自动释放池。

第二个 Observer 监听 kCFRunLoopBeforeWaiting 以及 kCFRunLoopExit 两个状态。在 RunLoop 即将进入休眠时释放旧池,创建新池。在 RunLoop 要退出时释放池子。

让一个子线程一直运行

- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建子线程
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil];
    [self.thread start];
}

- (void)threadMain {
    [[NSThread currentThread] setName:@"myThread"]; 
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
}

我在 myThread 线程中创建了一个 RunLoop,这个线程就不会自动销毁了。我们可以暂停程序看一下它的线程堆栈:

总结


对于 RunLoop 的了解其实还很浅,只能有一个大概的结构,不过了解了这些内容对于整个App运行的过程还是会更加深入一些了。

相关文章

  • 初次接触 RunLoop

    最近看了两位大神关于RunLoop的一些资料,对RunLoop算是有了一个初步的认识,在这里整理总结一下自己对于R...

  • 初次接触

    初次接触: 赤司篇: (单方面的一见钟情,会有结果吗?) (你的设定:胆小,谨慎(拜托,不是降旗,也不是樱井),不...

  • Hi,runloop 交个朋友吧

    developer:hi,runloop 初次见面,交个朋友吧?runloop:你好,很高兴认识你! develo...

  • python初次接触

    都是在网站学习的,感谢作者分享 安装python 没有Mac,只有windows和虚拟机里面的Linux wind...

  • saltstack初次接触

    先使用saltstack实现一些功能 查询资料去了解stackstack 相关拓展 使用saltstack实现简单...

  • solidity初次接触

    初次接触solidity 我首先使用的ide是以太坊官方推荐的Remix他有一下好处 模拟了以太坊的网络,交易等优...

  • 初次接触PHP

    数据库的连接

  • Swift 初次接触

    简介 最近几天接触了Swift这门新的计算机编程语言,感觉比起Objective-C,有了跟进一步的优化,让程序开...

  • 二. 初次接触

    中间过了一年,我和她都只是同学关系,我俩都很冷淡,这一年有过一次交集,也许她忘了。她坐在和我隔一条走廊的斜后排,...

  • 初次接触茶道

    第一次接触茶道,是零五年的元旦。那一天我在单位值班,傍晚回家的路上,想着应该给师兄打个电话,问候一下。随即在车上拨...

网友评论

    本文标题:初次接触 RunLoop

    本文链接:https://www.haomeiwen.com/subject/radrlttx.html