iOS面试基础知识点

浅谈 iOS 中的 RunLoop

2016-11-08  本文已影响139人  _凉风_

1)作用

2)RunLoop 对象

iOS 有两套 API 访问和使用 RunLoop

3)RunLoop 和 线程

4)RunLoop 相关类

I. CFRunLoopModeRef「RunLoop的运行模式」

系统默认注册了 5 个Mode

  1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  1. kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode
  2. UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  3. GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到

II. CFRunLoopTimerRef「基于时间的触发器,基本上就是 NSTimer」

1.已经自动添加到 RunLoop 中,默认模式是 kCFRunLoopDefaultMode

// 由于 CFRunLoopTimerRef 和 NSTimer 可以混用,这里使用 NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 以下 2 中的代码等价于上面的代码

2.修改模式

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

// case1:定时器只运行在 NSDefaultRunLoopMode 下,一旦RunLoop进入其他模式,这个定时器就不会工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
// case2:定时器会跑在标记为 common modes 的模式下
// 标记为common modes的模式:UITrackingRunLoopMode 和 kCFRunLoopDefaultMode
[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

III. CFRunLoopSourceRef「事件源,输入源」

按照官方文档分类:

  • Port-Based Sources 基于端口,和其他线程交互内核消息
  • Custom Input Sources 自定义
  • Cocoa Perform Selector Sources 用于处理 performSelector 函数

按照函数调用栈分类:

IV. CFRunLoopObserverRef「观察者,监听 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
};

CF「CoreFundation」的内存管理

  1. 凡事带有 Create、Copy、Retain等字眼的函数,创建出来的对象,最后都要做一次 release
  2. release函数:CFRelease(要释放的对象);
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});

// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
// CF开头的函数不受 ARC控制,带 Create的 要释放
// 释放Observer
CFRelease(observer);

5)RunLoop 处理事件的步骤

每次运行 RunLoop,线程的RunLoop对自动处理之前未处理的消息,并通知相关的观察者。

I. 步骤如下

  1. 看 Mode是否为空,若不空,通知 Observer RunLoop 已经启动<b>(之后创建一个自动释放池)</b>

  2. 通知 Observer「观察者」

    • 即将开始的 定时器「Timer」
    • 即将启动的 非基于端口的源「Source0」
  3. 启动准备好的任何 非基于端口的源「Source0」

  4. 如果 基于端口的源「Source1」 准备好并处于等待状态,立即启动 → 步骤 8

  5. 通知 Observer线程 → 休眠 <b>(休眠前会 销毁自动释放池,然后在创建自动释放池)</b>

  6. 以下任意事件 可以唤醒 已经休眠的程序

    • 基于端口的源「Source1」 接收到事件
    • 定时器启动
    • RunLoop 设置的循环时间超时
    • RunLoop 被唤醒
  7. 通知 Observer线程 → 唤醒

  8. 处理 未处理的 事件

    • 定义的定时器启动,处理定时器事件,重启RunLoop → 步骤2
    • 输入源/时间源 启动,传递信息
    • RunLoop 被显式唤醒 且 时间没超过RunLoop固定循环的时间,重启RunLoop → 步骤2
  9. 通知 Observer RunLoop 结束<b>(销毁自动释放池)</b>

II. 步骤图例

Paste_Image.png

III. 自动释放池什么时候释放?

通过 Observer监听 RunLoop的状态,一旦监听到RunLoop即将进入睡眠等待状态「kCFRunLoopBeforeWaiting」就释放自动释放池

6)RunLoop 应用

I. 某些事件「行为、任务」在特定模式下执行

大图渲染耗时,这时候多线程多任务可能会造成卡顿,下载完后不急于显示,而是等其他线程不忙时显示

// 只在 NSDefaultRunLoopMode 主线程模式下显示图片,一旦RunLoop进入其他模式,这个函数不会执行
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

II. 常驻线程

适用于:经常要做后台操作,频繁开启线程的情况,让一个线程常驻,可以避免频繁的开启使用线程的麻烦。等待其他线程发消息,处理事件

@autoreleasepool{
    // 方法一
    // Mode 里没有 任何东西的 RunLoop 会马上退出,这里随便加点东西,防止退出
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    // 开启 RunLoop 线程
    [[NSRunLoop currentRunLoop] run];
}

// 方法二、不推荐
while(flag){ [[NSRunLoop currentRunLoop] run]; }

III. 添加 Observer监听 RunLoop的状态

比如,监听点击事件的处理,在所有点击事件之前做一些处理

上一篇下一篇

猜你喜欢

热点阅读