iOS开发进阶:RunLoop

栏目: IOS · 发布时间: 6年前

内容简介:所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。在

Run loop 表示一个循环程序会一直运行。通过内部维护事件循环( Event Loop )来对事件/消息进行管理的一个对象。事件循环模型的特点: 在没有消息处理时,线程进入休眠状态以避免资源占用,在有消息时,立刻被唤醒执行任务。

所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

iOS 系统中提供了两个这样的对象:

  1. NSRunLoop : 基于 CFRunLoopRef 封装,提供面向对象的 API ,但是不是线程安全的。
  2. CFRunLoopRef :是在 CoreFoundation 框架中,提供了纯 C 的API,是线程安全。

main 函数为什么不会退出?

应用程序中,启动后执行下面代码:

int main(int argc, char * argv[]) {
   @autoreleasepool {
       return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
   }
}

因为调用 UIApplicationMain 启动了一个 Runloop 。有接收消息进行处理,没有消息进入等待。

RunLoop 和 线程的关系

RunLoop和线程是一一对应的。

RunLoop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];

RunLoop数据结构

CoreFoundation 中,提供了5个类:

  1. CFRunLoopRef
  2. CFRunLoopModeRef
  3. CFRunLoopSourceRef
  4. CFRunLoopTimerRef
  5. CFRunLoopObserverRef

他们之间的关系如下:

iOS开发进阶:RunLoop

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

结构体定义

CFRunLoop 结构体如下:

iOS开发进阶:RunLoop

  • pthread : 与线程相关,一一对应。
  • currentMode :当前RunLoop所处的模式
  • modes 集合 CFMutableSetRef
  • commonModes : CFMutableSetRef
  • commonModeItems : 集合,包含多个 ObserverTimerSource

Mode

Mode 代表 RunLoop 的不同运行模式,运行时只能选择一种模式运行作为 currentMode ,如果想切换需要退出当前循环,重新选择 Mode 在进入循环。为什么这样设计呢,为了隔离开不同的任务,互不影响。

CFRunLoopMode结构体:

iOS开发进阶:RunLoop

RunLoopMode 中包含的内容:

  • name : 名称 NSDefaultRunLoopMode
  • source0 :处理点击事件, performSelector:onThread:withObject: 方法。
  • source1 :基于Port的线程间通信,系统事件捕捉。
  • observers 数组,监听状态,UI刷新,自动释放池
  • timers 数组,NSTimer

一个 Mode 对应多个 Source/Timer/ObserverRunLoop 只能接受到当前Mode中添加的事件。

两种常用的 Mode :

  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode) : APP的默认Mode,通常主线程是这个模式。
  • UITrackingRunLoopMode : 界面跟踪 Mode ,用户 ScrollView 追踪触摸滑动,保证界面滑动是不受其他 Mode 的影响。

注意: NSRunLoopCommonMode 不是实际存在的 Mode 。是同步 Source/Timer/Observer 到多个 Mode 中的一种技术方案。

RunLoop的mode作用是什么?

mode 主要是用来指定事件在运行循环中的优先级的,分为:

NSDefaultRunLoopModekCFRunLoopDefaultMode
UITrackingRunLoopMode
UIInitializationRunLoopMode
NSRunLoopCommonModeskCFRunLoopCommonModes

苹果公开提供的 Mode 有两个:

NSDefaultRunLoopModekCFRunLoopDefaultMode)
NSRunLoopCommonModeskCFRunLoopCommonModes

Source

CFRunLoopSourceRef 是事件产生的地方。有两个版本 Source0Source1 :

  • source0 : 手动唤醒线程。 触摸事件, performSelector:onThread:withObject: 方法。
  • source1 :自动唤醒线程,基于Port的线程间通讯 ,系统事件的捕捉。

Timer

CFRunLoopTimerRef 基于时间的触发器,可以与NSTimer进行转换。 performSelector:withObject:afterDelay:

Observer

CFRunLoopObserverRef 是观察者,每个 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
};

上面的 Source/Timer/Observer 被统称为 mode item ,一个 item 可以被同时加入多个 mode 。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"KCFRunLoopEntry");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"Timers");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"Sources");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"BeforeWaiting");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"AfterWaiting");
                break;
            case kCFRunLoopExit:
                NSLog(@"Exit");
                break;
            default:
                break;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);

RunLoop运行逻辑

iOS开发进阶:RunLoop

iOS开发进阶:RunLoop

RunLoop的应用

1. 监控线程生命周期
2. 解决NSTimer在滑动停止工作的问题
3. 监控应用卡顿
4. 性能优化

滑动TableView的时候定时器为什么会失效?

因为滑动TableView的时候,RunLoop发生了Mode切换。

kCFRunLoopDefaultMode —> UITrackingRunLoopMode

解决方法:将Timer添加到RunLoop使用 commonMode 标记设置。

// 这种方式添加定时器不会出现,Timer停止的问题。
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"%d", count ++);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// NSRunLoopCommonModes 并不是一个真的模式,而是一个标记。

如何实现常驻线程?

经常使用子线程时,可以创建一个常驻子线程,可以节省系统资源。下面看创建常驻子线程:

BOOL shouldKeepRunning = YES; // global
- (void)viewDidLoad {
    [super viewDidLoad];
    // LEThread继承自NSThread重写dealloc方法,打印销毁信息。
    self.thread = [[LEThread alloc] initWithBlock:^{
        // 用于线程保活,不做具体的任务。
        // 线程一直在运行
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//        [[NSRunLoop currentRunLoop] run]; // 开启一个无限的循环,无法停止。
        NSRunLoop *theRL = [NSRunLoop currentRunLoop];
        while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
        NSLog(@"%s ---end----", __func__);
    }];
   // 启动子线程
    [self.thread start];
}

//停止线程
// 停止子线程RunLoop,该方法需要在子线程中调用。
- (void)stop {
    shouldKeepRunning = NO; // 标志
    CFRunLoopStop(CFRunLoopGetCurrent()); // 释放当前RunLoop。
    NSLog(@"%s", __func__);
}
  1. 为当前线程开启一个RunLoop。
  2. 向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环。
  3. 启动该RunLoop。

实例代码: GitHub

小结

RunLoop

本文并没有深入研究RunLoop,只是梳理RunLoop相关的知识点。如果想深入学习推荐《小马哥底层课程》。

GNUstep

参考


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Programming in Java

Introduction to Programming in Java

Robert Sedgewick、Kevin Wayne / Addison-Wesley / 2007-7-27 / USD 89.00

By emphasizing the application of computer programming not only in success stories in the software industry but also in familiar scenarios in physical and biological science, engineering, and appli......一起来看看 《Introduction to Programming in Java》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码