likes
comments
collection
share

如何理解iOS的Runloop?

作者站长头像
站长
· 阅读数 24

Runloop存在的价值

    1. 我们思考一下:程序为什么可以处理用户的各种事件,而且可以保证程序不退出?答案:就是因为程序里面有个系统的默认运行循环,所以能保证程序不退出,并且处理用户的各种事件

    1. 想要了解为什么要使用运行循环,我们可以从使用运行循环的目的来了解,使用了运行循环我们可以:
    • a. 保证程序不退出 ;
    • b. 负责处理输入事件 ;
    • c. 如果没有事件发生,会让程序进入休眠状态。
  • 从上面可以得出结论,为什么要使用运行循环,因为这是一个 APP 的基本,没有运行循环,就没有 APP 的正常运行。


    1. 通过上面的了解我们可以总结运行循环的优点:有事情就做事情,没事情就休息;优点:节省 CPU 资源、提高程序性能;

Runloop与线程

    1. 每一个线程内部都有一个消息循环。只有主线程的消息循环默认开启 , 子线程的消息循环默认不开启,一个运行循环对应着一条唯一的线程,如何让子线程不死 ,给这条子线程开启一个运行循环,子线程的 runloop 需要手动创建 , 需要手动开启
    1. 线程在执行中的休眠和激活就是由 RunLoop 对象进行管理的
  • 3.RunLoop 是用来管理线程的
    1. 每一个线程都有一个 RunLoop 对象。可以通过具体的方法去获得
    1. 但是需要注意:虽然每一个线程都可以获取 RunLoop 对象,但是并不是每一个线程中都有实例对象,我们可以这样理解:如果我们不获取 RunLoop ,这个 RunLoop 就不存在,我们获取时,如果不存在,就会去创建。在主线程中,这个 MainRunLoop 是默认创建并运行激活的
    1. 每条线程都有唯一的一个与之对应的 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 的源)通过内核和其他线程通信,接收、分发系统事件
      1. 新:基于函数的调用栈来进行划分的( source0 和 source1 )
      • source0 是非基于端口的 , 是用户自己手动触发的操作 , 比如触摸滑动等操作 . 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source) ,将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop ,让其处理这个事件。
      • source1 是系统内部的一些端口触发的事件

  • 3.定时源

    • 1 )一般用来处理同步事件的
    • 2 )定时源即 NSTimer ( add timer )
      1. CFRunLoopTimerRef 是基于时间的触发器
      1. CFRunLoopTimerRef 基本上说的就是 NSTimer ,它受 RunLoop 的 Mode 影响
      1. GCD 的定时器不受 RunLoop 的 Mode 影响
    • 6 )一个 mode 里面可以添加多个 NSTimer ,也就是说以后当创建 NSTimer 的时候,可以指定它是在什么模式下运行的。
  • 默认的运行循环模式,当有触发事件发生时,一般时钟会暂时停止。这是大部分的运行循环模式所采用的


[[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSDefaultRunLoopMode]; 

  • NSRunLoopCommonModes 这个模式是 “ 监听滚动模式 ” ,触摸屏幕拖拽也不会管你,还是继续执行时钟

[[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSRunLoopCommonModes];
	

  • 4.performSelector 源
      1. 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop ,则这个方法会失效。
      1. 当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
      1. performSelector 应用:可以让某些事件(行为、任务)在特定模式下执行 ; 有时候图片比较大,渲染到屏幕耗费时间,会造成界面卡顿,可以让图片在 UIScrollView 滚动完之后执行

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:2 inModes:@[NSDefaultRunLoopMode]]; 


    1. 自定义源

    1. 基于端口源

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的使用原则
      1. 同一时间段只存在一种mode
      1. 切换 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 的模式: 

UITrackingRunLoopModeNSDefaultRunLoopMode 

[[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启动的三种方式:

    1. 无条件的 ---- 无条件的进入 Runloop 是最简单的方法,但也最不推荐使用的。因为这样会使你的线程处在一个永久的循环中,这会让你对 Runloop 本身的控制很少。你可以添加或删除输入源和定时器,但是退出 Runloop 的唯一方法是杀死它。没有任何办法可以让这 Runloop 运行在自定义模式下。

[[NSRunLoop currentRunLoop] run]; 

    1. 设置超时时间 ---- 替代无条件进入 Runloop 更好的办法是用预设超时时间来运行 Runloop ,这样 Runloop 运作直到某一事件到达或者规定的时间已经到期。如果是事件到达,消息会被传递给相应的处理程序来处理,然后 Runloop 退出。你可以重新启动 Runloop 来等待下一事件。如果是规定时间到期了,你只需简单的重启 Runloop 或使用此段时间来做任何的其他工作。 2 秒钟之后结束

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; 

    1. 特定的模式 ---- 除了超时机制,你也可以使用特定的模式来运行你的 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
评论
请登录