如何理解iOS的Runloop?
Runloop存在的价值
-
- 我们思考一下:程序为什么可以处理用户的各种事件,而且可以保证程序不退出?答案:就是因为程序里面有个系统的默认运行循环,所以能保证程序不退出,并且处理用户的各种事件
-
- 想要了解为什么要使用运行循环,我们可以从使用运行循环的目的来了解,使用了运行循环我们可以:
- a. 保证程序不退出 ;
- b. 负责处理输入事件 ;
- c. 如果没有事件发生,会让程序进入休眠状态。
-
从上面可以得出结论,为什么要使用运行循环,因为这是一个 APP 的基本,没有运行循环,就没有 APP 的正常运行。
-
- 通过上面的了解我们可以总结运行循环的优点:有事情就做事情,没事情就休息;优点:节省 CPU 资源、提高程序性能;
Runloop与线程
-
- 每一个线程内部都有一个消息循环。只有主线程的消息循环默认开启 , 子线程的消息循环默认不开启,一个运行循环对应着一条唯一的线程,如何让子线程不死 ,给这条子线程开启一个运行循环,子线程的 runloop 需要手动创建 , 需要手动开启
-
- 线程在执行中的休眠和激活就是由 RunLoop 对象进行管理的
- 3.RunLoop 是用来管理线程的
-
- 每一个线程都有一个 RunLoop 对象。可以通过具体的方法去获得
-
- 但是需要注意:虽然每一个线程都可以获取 RunLoop 对象,但是并不是每一个线程中都有实例对象,我们可以这样理解:如果我们不获取 RunLoop ,这个 RunLoop 就不存在,我们获取时,如果不存在,就会去创建。在主线程中,这个 MainRunLoop 是默认创建并运行激活的
-
- 每条线程都有唯一的一个与之对应的 RunLoop 对象
- 7.判断runloop是否有效的关键是有没有事件源或者定时源
Runloop的工作原理
认识Runloop
- NSRunloop
- CFRunLoopRef
- 两种 API 的区别 1 : NSRunloop 线程不安全, CFRunLoopRef 线程安全; NSRunLoop 是 Cocoa 框架中的类,与之对应的是在 Core Fundation 中有一个 CFRunLoopRef 类。这两者的区别是前者不是线程安全的,而 CFRunLoopRef 是线程安全的。所以: NSRunloop 不能调用其他线程的方法 .
- 两种 API 的区别 2 : CFRunLoopRef 创建一个 timer 必须添加到 runloop 才会执行 , 添加的时候要指定模式 defaurce 模式 , 不对程序做任何操作 timer 就会后台运行 , 当我进行操作的时候 runloop 模式就会从默认模式切换到其他模式 , 假如说我操作 scrollerView 它就会从 default 模式切换到 tracking 模式。而 roonloop 同一时刻只能执行一种模式 .
- NSRunLoop 是基于 CFRunLoopRef 的一层 OC 包装,提供了面向对象的 API ,但是这些 API 不是线程安全的。所以要了解 RunLoop 内部结构,需要多研究 CFRunLoopRef 层面的 API ( Core Foundation 层面)
- RunLoop 类 五个类
- CFRunLoopRef
- CFRunLoopModeRef // 模式
- CFRunLoopSourceRef // 事件源
- CFRunLoopTimerRef // 时间源
- CFRunLoopObserverRef // 观察者
运行循环本质
- 从字面上看,就是运行循环跑圈
- 其实它内部就是 do-while 循环,在这个循环内部不断地处理各种任务(比如 Source 、 Timer 、 Observer )
- 循环体的开始需要检测是否有需要处理的事件,如果有则去处理,如果没有则进入睡眠以节省 CPU 时间
- 什么是输入源?
- 什么是模式?
- 什么是观察者
- 我们思考一下:程序为什么可以处理用户的各种事件,而且可以保证程序不退出?答案:就是因为程序里面有个系统的默认运行循环,所以能保证程序不退出,并且处理用户的各种事件
- 想要了解为什么要使用运行循环,我们可以从使用运行循环的目的来了解,使用了运行循环我们可以:
- a. 保证程序不退出 ;
- b. 负责处理输入事件 ;
- c. 如果没有事件发生,会让程序进入休眠状态。
- 从上面可以得出结论,为什么要使用运行循环,因为这是一个 APP 的基本,没有运行循环,就没有 APP 的正常运行。
- 通过上面的了解我们可以总结运行循环的优点:有事情就做事情,没事情就休息;优点:节省 CPU 资源、提高程序性能;
- 系统默认就是一个运行循环,可以保证程序不死掉;系统默认运行循环在主线程
- 每一个线程内部都有一个消息循环。只有主线程的消息循环默认开启 , 子线程的消息循环默认不开启,一个运行循环对应着一条唯一的线程,如何让子线程不死,给这条子线程开启一个运行循环,子线程的 runloop 需要手动创建 , 需要手动开启
- 线程在执行中的休眠和激活就是由 RunLoop 对象进行管理的
- RunLoop 是用来管理线程的
- 每一个线程都有一个 RunLoop 对象。可以通过具体的方法去获得
- 但是需要注意:虽然每一个线程都可以获取 RunLoop 对象,但是并不是每一个线程中都有实例对象,我们可以这样理解:如果我们不获取 RunLoop ,这个 RunLoop 就不存在,我们获取时,如果不存在,就会去创建。在主线程中,这个 MainRunLoop 是默认创建并运行激活的
- 每条线程都有唯一的一个与之对应的 RunLoop 对象
- 运行循环的生命周期:在第一次获取时创建,在线程结束时销毁
- 每次运行一个 RunLoop ,你指定(显式或隐式) RunLoop 的运行模式。当相应的模式传递给 RunLoop 时,只有与该模式对应的 Input Source 才被监控并允许 RunLoop 对事件进行处理(与此类似,也只有与该模式对应的 Observers 才会被通知)
- runloop 就像是事件驱动,有事情做就唤醒,没有事做就睡眠
int main(int argc, char * argv[]) {
while (AppIsRunning) {
id whoWakesMe = SleepForWakingUp();
id event = GetEvent(whoWakesMe);
HandleEvent(event);
}
return 0;
}
- 使程序一直运行并接受用户输入
- 决定程序在何时应该处理哪些 Event
- 调用解耦( Message Queue )
- 节省 CPU 时间
Runloop工作流程
- 创建消息(即输入源);
- 指定该事件(源)在循环中运行的模式,并加入循环;
- 当事件的模式与消息循环的模式匹配的时候,消息才会运行。
- 运行逻辑总结:一个线程对应一个 runLoop, 主线程的 runloop 是程序一启动 , 默认就创建一个 runloop, 创建好了之后就会给它添加一些默认的模式 , 每个模式里面会有很多的 source /timer/observer , 添加好这些模式后 ,observer 就会监听主线程的 runloop, 进入 runloop 后 , 就开始处理事件 , 先处理 timer, 再处理 source0,source0 处理完之后再处理 source1, 当把这些所有的事件反复的处理完之后 , 如果没有事件了 , 那么 runloop 就会进入睡眠状态 , 当用户又触发了新的事件 , 就会唤醒 runloop, 唤醒 runloop 后回到第二步 , 重新处理新的 timer, 新的 source0, 新的 source1, 处理完后就睡眠 , 一直反复 , 当我们把程序关闭或者强退 , 这个时候 observer 就会监听都 runloop 退出了 .
输入源(mx:消息、时间-要做的事)
- 1.RunLoop 能处理的事件:输入源(事件源)、定时源 ;
- 输入源也就是用户的各种事件,如触摸事件、定时器事件 NSTimer 、选择器事件 selector;
- 输入源是为了接收消息;当为一个长期存活的现场配置 runloop 时,至少添加一个 input source 去接收消息
- CFRunLoopSourceRef 是事件产生的地方
- 2.输入源(事件源)
- 1 )一般用来处理异步事件的;
- 2 )两种分法
- 旧:按照苹果官方文档进行划分的
- 新:基于函数的调用栈来进行划分的( source0 和 source1 )
- 3 )旧:按照苹果官方文档进行划分的
- Cocoa Perform Selector Sources ( performSelector 源)
- Custom Input Sources (自定义源)
- Port-Based Sources (基于端口 Mach port 的源)通过内核和其他线程通信,接收、分发系统事件
-
- 新:基于函数的调用栈来进行划分的( source0 和 source1 )
- source0 是非基于端口的 , 是用户自己手动触发的操作 , 比如触摸滑动等操作 . 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source) ,将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop ,让其处理这个事件。
- source1 是系统内部的一些端口触发的事件
-
3.定时源
- 1 )一般用来处理同步事件的
- 2 )定时源即 NSTimer ( add timer )
-
- CFRunLoopTimerRef 是基于时间的触发器
-
- CFRunLoopTimerRef 基本上说的就是 NSTimer ,它受 RunLoop 的 Mode 影响
-
- GCD 的定时器不受 RunLoop 的 Mode 影响
- 6 )一个 mode 里面可以添加多个 NSTimer ,也就是说以后当创建 NSTimer 的时候,可以指定它是在什么模式下运行的。
-
默认的运行循环模式,当有触发事件发生时,一般时钟会暂时停止。这是大部分的运行循环模式所采用的
[[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSDefaultRunLoopMode];
- NSRunLoopCommonModes 这个模式是 “ 监听滚动模式 ” ,触摸屏幕拖拽也不会管你,还是继续执行时钟
[[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSRunLoopCommonModes];
- 4.performSelector 源
-
- 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop ,则这个方法会失效。
-
- 当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
-
- performSelector 应用:可以让某些事件(行为、任务)在特定模式下执行 ; 有时候图片比较大,渲染到屏幕耗费时间,会造成界面卡顿,可以让图片在 UIScrollView 滚动完之后执行
-
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:2 inModes:@[NSDefaultRunLoopMode]];
-
- 自定义源
-
- 基于端口源
mode模式(mx:拿到源就给他配置一个模式,是如何工作的模式)
- RunLoopMode 是一个集合,包括监听:事件源,定时器,以及需通知的 RunLoop observers
- CFRunloopModeRef 代表着 Runloop 的运行模式
- 一个 Runloop 中可以有多个 mode, 一个 mode 里面又可以有多个 source\observer\timer 等等
- 每次 runloop 启动的时候,只能指定一个 mode, 这个 mode 被称为该 Runloop 的当前 mode
- 如果需要切换 mode, 只能先退出当前 Runloop, 再重新指定一个 mode 进入; RunLoop 在某一时刻只能在一种模式下运行 , 更换模式时需要暂停当前的 Loop, 然后重启新的 Loop.
- 这样做主要是为了分割不同组的定时器等,让他们相互之间不受影响
- mode的使用原则
-
- 同一时间段只存在一种mode
-
- 切换 Mode 的原则:停止当前 Loop ,重启新 Loop
-
mode的种类:
- NSDefaultRunLoopMode :默认状态、空闲状态
//NSDefaultRunLoopMode 使用
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];
-
UITrackingRunLoopMode :滑动 ScrollView 时
-
UIInitializationRunLoopMode :私有, App启动时
-
NSRunLoopCommonModes : Mode 集合
// 若不希望 Timer 被 ScrollView 影响,需添加到 NSRunLoopCommonModes
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- NS 开头 (Cocoa)
- NSDefaultRunLoopMode 默认模式(最常用的循环模式) 默认 Mode, 通常主线程在这个模式下运行处理 modal panels ( The mode to deal with input sources other than NSConnection objects. This is the most commonly used run-loop mode. Available in iOS 2.0 and later. ) ,DefaultMode 是 App 平时所处的状态
- NSEventTrackingRunLoopMode 此模式下用于处理窗口事件 , 鼠标事件等 , 如组件拖动输入源 UITrackingRunLoopModes 不处理定时事件; TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态
- NSRunLoopCommonModes 普通模式(一组模式的集合 , 包括 NSDefaultRunLoopMode 和 UITrackingRunLoopMode )( Objects added to a run loop using this value as the mode are monitored by all run loop modes that have been declared as a member of the set of “common” modes; see the description of CFRunLoopAddCommonMode for details. );哪些 mode 默认久标记为 "Common" 属性:主线程的 RunLoop 里有两个预置的 Mode : kCFRunLoopDefaultMode 和 UITrackingRunLoopMode 。这两个 Mode 都已经被标记为 "Common" 属性。 此模式用于配置 ” 组模式 ” ,一个输入源与此模式关联,则输入源与组中的所有模式相关联。这是一组可配置的通用模式。将 input sources 与该模式关联则同时也将 input sources 与该组中的其它模式进行了关联。
- NSConnectionReplyMode 此模式用于处理 NSConnection 的回调事件 , :处理 NSConnection 事件,属于系统内部,用户基本不用
- NSModalPanelRunLoopMode 模态模式,此模式下, RunLoop 只对处理模态相关事件
- CF 开头 (Core Foundation)1 )系统默认注册了 5 中 mode
- kCFRunLoopDefaultMode App 的默认 Mode ,通常主线程是在这个 Mode 下运行;缺省情况下,将包含所有操作,并且大多数情况下都会使用此模式 , 几乎包括所有输入源 ( 除 NSConnection), 对应 NSDefaultRunLoopMode
- UITrackingRunLoopMode 界面跟踪 Mode ,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode 在刚启动 App 时第进入的第一个 Mode ,启动完成后就不再使用
- GSEventReceiveRunLoopMode 接受系统事件的内部 Mode ,通常用不到
- kCFRunLoopCommonModes 这是一个占位用的 Mode ,不是一种真正的 Mode
mode切换
-
模式切换1 : NSDefaultRunLoopMode 切换为 NSEventTrackingRunLoopMode
- 当你创建一个 Timer 并加到 DefaultMode 时, Timer 会得到重复回调,但此时滑动一个 TableView 时, RunLoop 会将 mode 切换为 TrackingRunLoopMode ,这时 Timer 就不会被回调,并且也不会影响到滑动操作。
- 当设置事件模式为 NSDefaultRunLoopMode 时,拖动 UITextView 界面 , 定时源停止运行;当停止拖动,定时源又继续运行 ; 当设置事件模式为 NSRunLoopCommonModes 时,拖动 UITextView 界面 , 定时源持续运行不受影响。此外,当设置事件模式为 NSRunLoopCommonModes 时,未拖动 UITextView 界面时,消息循环的模式为 kCFRunLoopDefaultMode ,当拖动 UITextView 界面时,消息循环的模式自动变为 UITrackingRunLoopMode
-
模式切换例2
- 调用了 scheduledTimer 返回的定时器,已经自动被添加到当前 runLoop 中,而且是 NSDefaultRunLoopMode
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- 当你在滑动 ScrollView 的时候上面的 timer 会失效 , 原因是 Timer 是默认加在 NSDefalutRunLoopMode 上的 , 而滑动 ScrollView 后系统把 RunLoop 切换为 UITrackingRunLoopMode, 所以 timer 就不会执行了 . 解决方法是把该 Timer 加到 NSRunLoopCommonModes 下 , 这样即使滑动 ScrollView 也不会影响 timer 了 .
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- 模式切换例3
- 另外还有一个 trick 是当 tableview 的 cell 从网络异步加载图片 , 加载完成后在主线程刷新显示图片 , 这时滑动 tableview 会造成卡顿 . 通常的思路是 tableview 滑动的时候延迟加载图片 , 等停止滑动时再显示图片 . 这里我们可以通过 RunLoop 来实现 .
[self.cellImageView performSelector:@sector(setImage:)withObject:downloadedImageafterDelay:0inModes:@[NSDefaultRunLoopMode]];
- 当 NSRunLoop 为 NSDefaultRunLoopMode 的时候 tableview 肯定停止滑动了 , why? 因为如果还在滑动中 , RunLoop 的 mode 应该是 UITrackingRunLoopMode.
让一个事件同时在两个 mode 都有回调方法
- 有时你需要一个 Timer ,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode 。
- 还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 "commonModeItems" 中。 "commonModeItems" 被 RunLoop 自动更新到所有具有 "Common" 属性的 Mode 里去
- 方式 1:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
- 定时器只运行在 NSDefaultRunLoopMode 下,一旦 RunLoop 进入其他模式,这个定时器就不会工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
- 定时器只运行在 UITrackingRunLoopMode 下,一旦 RunLoop 进入其他模式,这个定时器就不会工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
- 方式 2 :
// 定时器会跑在标记为 common modes 的模式下 // 标记为 common modes 的模式:
UITrackingRunLoopMode 和 NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
runloop观察者
RunLoop 观察者介绍
- CFRunLoopObserverRef 是观察者,能够监听 RunLoop 的状态改变
- Observer 是监听 RunLoop 状态的, CoreFunction 向线程添加 runloop observers 来监听事件,意在监听事件发生时来做处理。
- 线程除了处理输入源, RunLoop 也会生成关于 Run Loop 行为的通知( notification )。 RunLoop 观察者( Run-Loop Observers )可以收到这些通知,并在线程上面使用他们来作额外的处理;如果 RunLoop 没有任何源需要监视的话,它会在你启动之际立马退出。
观察者状态
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入 Runloop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 NSTimer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Sources
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出 runloop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有状态改变
};
- 第一个参数 : 用于分配该 observer 对象的内存 CFAllocatorGetDefault
- 第二个参数 : 用以设置该 observer 所要关注的的事件 kCFRunLoopAllActivities
- 第三个参数 : 用于标识该 observer 是在第一次进入 run loop 时执行 , 还是每次进入 run loop 处理时均执行 YES
- 第四个参数 : 用于设置该 observer 的优先级 0
- 第五个参数 : observer 监听到事件时的回调 block
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch(activity){
case kCFRunLoopEntry:
NSLog(@" 即将进入 loop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@" 即将处理 timers");
break;
case kCFRunLoopBeforeSources:
NSLog(@" 即将处理 sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@" 即将进入休眠 ");
break;
case kCFRunLoopAfterWaiting:
NSLog(@" 刚从休眠中唤醒 ");
break;
case kCFRunLoopExit:
NSLog(@" 即将退出 loop");
break;
default:
break;
}
});
参数解析
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
- 第一个参数 : 给哪个 RunLoop 添加监听
- 第二个参数 : 需要添加的 Observer 对象
- 第三个参数 : 在哪种模式下监听
释放观察者
CFRelease(observer);
添加观察者过程
- 1 )创建一个观察者
// 创建一个 runloop 监听者
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities,YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@” 监听 RunLoop 状态改变 —%zd”,activity);
});
- 2 )添加创建的观察者
CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer,kCFRunLoopDefaultMode);// 添加观察者:监听 RunLoop 的状态
- 3 )释放创建的观察者
CFRelease(observer);// 释放
runloop启动的三种方式:
-
- 无条件的 ---- 无条件的进入 Runloop 是最简单的方法,但也最不推荐使用的。因为这样会使你的线程处在一个永久的循环中,这会让你对 Runloop 本身的控制很少。你可以添加或删除输入源和定时器,但是退出 Runloop 的唯一方法是杀死它。没有任何办法可以让这 Runloop 运行在自定义模式下。
[[NSRunLoop currentRunLoop] run];
-
- 设置超时时间 ---- 替代无条件进入 Runloop 更好的办法是用预设超时时间来运行 Runloop ,这样 Runloop 运作直到某一事件到达或者规定的时间已经到期。如果是事件到达,消息会被传递给相应的处理程序来处理,然后 Runloop 退出。你可以重新启动 Runloop 来等待下一事件。如果是规定时间到期了,你只需简单的重启 Runloop 或使用此段时间来做任何的其他工作。 2 秒钟之后结束
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
-
- 特定的模式 ---- 除了超时机制,你也可以使用特定的模式来运行你的 Runloop 。模式和超时不是互斥的,他们可以在启动 RunLoop 的时候同时使用。模式限制了可以传递事件给 Runloop 的输入源的类型。暂停当前处理的流程,转而处理其他输入源,当 date 设置为 NSDate distantFuture ,所以除非处理其他输入源结束,否则永不退出处理暂停的当前处理的流程
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
结束的三种方式:
- 给 RunLoop 设置超时时间
- 通知 RunLoop 停止 ---- 如果可以配置的话,推荐使用第一种方法。指定一个超时时间可以使 RunLoop 退出前完成所有正常操作,包括发送消息给 RunLoop 观察者。
- 使用 CFRunLoopStop 来显式的停止 RunLoop 和使用超时时间产生的结果相似。 RunLoop 把所有剩余的通知发送出去再退出。与设置超时的不同的是你可以在无条件启动的 RunLoop 里面使用该技术。
- 运行循环 移除输入源
- 输入源被注册到 RunLoop 中时会有方法进行 remove 。但是定时器没有 remove ,但是它的 invalidate 方法可以将其从 RunLoop 中移除。 invalidate 是重要的也是唯一的将定时器从 RunLoop 中注销的方法,所以如果我们创建了定时器,就一定要再不适用的时候调用 invalidate 方法。
- 自动释放池 , 什么时候创建和释放 ?
- (1) 第一次创建 : 是在 runloop 进入的时候创建 对应的状态 = KCFRunLoopEntry
- (2) 最后一个退出 , 是在 runloop 退出的时候 对应的状态 = KCFRunLoopExit
- (3) 其他的创建和释放
- 每次睡觉的时候会释放前自动释放池 , 再创建一个新的
- 即将进入睡眠的时候 , 先释放上一次创建的自动释放池 , 然后再创建一个新的释放池
runloop使用的例子
runloop解决cell滑动timer停止
- 可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决
- 将timer添加到NSDefaultRunLoopMode中
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerTick:)
userInfo:nil
repeats:YES];
//然后再添加到NSRunLoopCommonModes里
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:@selector(timerTick:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
runloop让crash的app起死回生
- 接到 Crash 的 Signal 后手动重启 RunLoop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
for (NSString *mode in allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
runloop让tableview延迟加载图片
- 以前加载都是在 cellforindexpathrow 加载,现在的思路是
UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:) withObject:downloadedImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
- setupJJPerformselecterOne tableview延迟加载图片
- (void)setupJJPerformselecterOne
{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(taskOne) object:nil];
[thread start];
}
-(void)taskOne
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];// 直到 1 秒后执行
NSLog(@"taskOne is running");
}
runloop子线程定时任务
- // 只执行 4 次, 1 秒执行一次
@property(nonatomic,strong)NSTimer *timer;
- (void)handleSomeTimers:(float)startTime interval:(float)interval
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:startTime]
interval:interval
target:self
selector:@selector(runTimerWithRepeatCount)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
- (void)runTimerWithRepeatCount{
count ++;
if (count == 4) {
[self.timer invalidate];
}
NSLog(@"runTimer__on thread %@",[NSThread currentThread]);
}
runloop实现子线程常驻
- runloop 运行循环 runloop 应用场景【重要】【配合 NSTimer 】【子线程常驻】(后台常驻运行 runTimer 方法;每个 0.1 秒执行一次方法;)
- 子线程常驻的作用是,在 app 启动后,有一个线程在后面一直常驻,可以让这个线程定时去处理事情
- setupJJTimerRunLoop 子线程:后台常驻运行 runTimer 方法;每个 0.01 秒执行一次方法;
@property(nonatomic,strong)NSTimer *timer;
- (void)setupJJTimerRunLoop
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.01]
interval:0.01
target:self
selector:@selector(runTimer)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];// 默认不开,所以这里要手动打开
});
}
- (void)runTimer
{
NSLog(@"runTimer__on thread %@",[NSThread currentThread]);
}
runloop在AFN中使用
- 【总结: AFN 的使用 runloop 的思路是,先创建一个线程,并把这个线程加入到 runloop 中,在 runloop 监听 port 端口,触发操作】
- 这是创建一个常驻服务线程的很好方法
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];// 开启循环,监听 port ,触发操作
[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;
}
转载自:https://juejin.cn/post/6869669153922367496