iOS学习笔记整理-纸上得来终觉浅 写成文档不怕丢:-D
RunLoop In Cocoa
RunLoop 顾名思义,可以通俗的将之理解为一个无限Run的循环(loop),得益于这种机制,OC可以捕获程序内的事件并响应。
当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程。RunLoop就是控制线程生命周期并接收事件进行处理的机制。
RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。 在Fundation框架下我们平时使用的的是NSRunloop类,他是Core Fundation框架下CFRunloop类的上层封装。
CFRunloop构成
CFRunloop和Thread是一一对应的关系,每一个CFRunloop对象能且仅能运行在一种CFRunloopMode下,而CFrunloopMode内部又是由若干个CFRunloopSource、CFRunloopTimer、CFRunloopObserver对象以Array的形式组成的。
CFRunloopMode
- Runloop在同一时段下能且只能运行在一种Mode下;
- 想要更换当前mode,则需要停止当前loop,重启新的loop;
- mode是iOS App滑动顺畅的关键
- 开发者可以自定义mode
CFRunloopMode类型
- NSDefaultRunloopMode 默认状态,空闲状态
- UITrackingRunloopMode 滑动ScrollView时的状态
- UIInitializationRunloopMode (私有状态)app启动时所处的状态
- NSRunloopCommentMode 自定义状态,一般是上面状态的集合
- GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
RunLoop可以通过[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]来指定在一段时间内的运行模式。如果不指定的话,RunLoop默认会运行在Default下(不断重复调用runMode:NSDefaultRunLoopMode beforDate:)
在主线程启动一个计时器Timer,然后拖动UITableView或者UIScrollView,计时器不执行。这是因为,为了更好的用户体验,在主线程中Event tracking模式的优先级最高。在用户拖动控件时,主线程的Run Loop是运行在Event tracking Mode下,而创建的Timer是默认关联为Default Mode,因此系统不会立即执行Default Mode下接收的事件。 解决方法:
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerFireMethod:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//或
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
[timer fire];
复制代码
RunLoopTimer
就是我们所理解的Timer计时器
RunloopSource
source可以理解为Runloop的数据抽象源(protocol); Runloop定义了两个version的Source:
- source0:用于处理APP内部事件,app自己负责管理/触发,常见的有UIEvent、CFSocket类等;
- source1:由Runloop和系统内核管理,mach Port驱动,常见的有CFMachPort、CFMessagePort等;
CFRunloopObserver
该对象用于向外部报告runloop当前状态的更改,框架中,很多机制都是由RunloopObserver触发的,比如CAAnimation动画。
CFrunloopObserver对外报告的状态枚举CF_OPTIONS共有6中,分别是
- 1 进入loop
- 2 准备Timer
- 3 准备执行Obsever
- 4 准备休眠
- 5 休眠结束将要唤醒
- 6 退出loop
RunLoopObserver与Autorelease Pool的关系
UIKit 通过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将这次 Loop 中产生的 Autorelease 对象释放。Runloop的挂起与唤醒
指定用于唤醒的 mach_port 端口调用 mach_msg 监听唤醒端口,被唤醒前系统内核将这个线程挂起,停留在 mach_msg_trap状态。 由另一个线程向内核发送这个端口的msg后,trap状态被唤醒,RunLoop继续工作。RunLoop支持的消息Events
1 支持接收处理输入源(Input Source)事件,包括:- 系统的Mach Port事件,是一种通讯事件
- 自定义输入事件
2 支持接受处理定时源(Timer)事件。
上图可以看出,Runloop必须添加input sources或 Timer sources用于保持监听循环,否则调用[runloop run]会直接返回,而不会进入循环让线程长驻。
因为如果没有添加任何输入源事件或Timer事件,线程会一直在无限循环空转中,会一直占用CPU时间片,没有实现资源的合理分配。 没有while循环且没有添加任何输入源或Timer的线程,线程会直接完成,被系统回收。
Run Loop主要有以下三个应用场景
- 维护线程的生命周期,让线程不自动退出
- 创建常驻线程,执行一些会一直存在的任务
- 在一定时间内监听某种事件,或执行某种任务的线程
AFNetworking中RunLoop的创建
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 这里主要是监听某个 port,目的是让这个 Thread 不会回收
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
复制代码