RunLoop: 移除常驻线程
概要 下面两篇文章:
都是在 iOS 平台上如何利用 RunLoop
的特性实现常驻线程.
可能你看到本篇文章的标题 移除常驻线程
, 有点不理解甚至觉得作者是不是有点神经病, 都已经需要常驻线程了, 为什么还要去退出呢?
实际应用场景中的确几乎遇不到这种情况, 所以, 本篇只是从技术的角度给大家分享 RunLoop
其他的一些知识点.
且耐住性子往下看…
为了不让大家误解, 统一一下相关的词汇和语境.
输入源, 包括 source 和 timer.
退出 RunLoop 指的是在常驻线程的方法中立即返回.
这里的 RunLoop 指的是子线程中的 RunLoop 不是主线程中的.
结合之前介绍常驻线程的文章来看现在的文章, 不然不好理解.
运行 RunLoop 的方法 这里有三种方式开启运行 RunLoop, 分别如下:
1 2 3 4 5 - (void)run; - (void)runUntilDate:(NSDate *)limitDate; - (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
看一下 API 文档如何解释 run
方法的:
1 2 3 4 5 Puts the receiver into a permanent loop, during which time it processes data from all attached input sources. If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
大致意思讲的是, 在没有任何输入源的情况下, run
方法会立即执行后退出, 不会保持线程的持久性, 换句话说, 在有输入源的情况下, 该方法会进入一个无限循环当中. 本质上, 在 NSDefaultRunLoopMode
模式下, 该方法是反复调用 runMode:beforeDate:
方法的.
在之前文章实现常驻线程的代码中, 我们使用了 run
方法来开启运行 RunLoop.
可以看出, 我们之前实现的常驻线程使用 run
方法是无法退出常驻线程的.
*runUntilDate:(NSDate )limitDate 方法
该方法 API 释义:
1 2 Runs the loop until the specified date, during which time it processes data from all attached input sources.
保证有输入源的情况下该方法启动的 RunLoop 可以在指定的日期内一直运行不会返回. 换句话说, 日期只要已到该方法就会立即退出 RunLoop.
示例代码:
1 2 3 4 5 6 7 8 //等同于 run, 可保证一直运行 [runLoop runUntilDate:[NSDate distantFuture]]; //立即返回, 退出 RunLoop [runLoop runUntilDate:[NSDate date]]; //然后过12秒后返回 [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:12.0f]];
**runMode:(NSString )mode beforeDate:(NSDate )limitDate 方法
该方法有两个参数 mode
和 limitDate
, mode
就是 RunLoop 的运行模式, limitDate
就是上面方法一样的释义即在指定的日期内.
1 Runs the loop once, blocking for input in the specified mode until a given date.
可以这么理解, 该方法开启的 RunLoop 处理完之后会立即返回(once), 如果在指定日期内事件还没处理, 在该日期后会立即返回. 换句话说, 如果指定的日期是当前的, 该方法执行后 RunLoop 会立即退出.
示例代码:
1 2 3 4 5 //立即返回 [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]]; //有事件到达处理后就返回,如果没有则过12秒返回 [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:12.0]];
这三个方法, 总结一下大概如下:
这三个方法在没有任何输入源情况下会都立即返回(退出 RunLoop), 不会等待.
run
可保证 RunLoop 在有输入源的情况下一直运行.
runUntilDate
可以通过设置超时时间来退出 RunLoop. 超时时间一过就会立即退出 RunLoop.
使用 runMode
方式启动的 RunLoop 会在处理完事件后或者超时后, 立即返回. 即可以通过设置超时时间或者使用 CFRunLoopStop
方法来退出 RunLoop.
移除常驻线程 先看一下, 在子线程执行的方法中如何实现的. 示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 - (void)asyncRun { @autoreleasepool { NSLog(@"veryitman--asyncRun. Current Thread: %@", [NSThread currentThread]); NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; _threadRunLoop = runLoop; // 保持常驻线程的方式1: source NSPort *port = [NSMachPort port]; _threadPort = port; [runLoop addPort:port forMode:NSRunLoopCommonModes]; NSLog(@"veryitman--asyncRun. Current RunLoop: %@", runLoop); // 执行其他逻辑 //... // 手动开启 RunLoop // [runLoop run]; while (!self.stopLoopRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) { // 这里是为了验证常驻线程是否已经退出 NSLog(@"--- asyncRun ----"); // 实际业务中, 建议使用空语句实现 ; //实现为空语句 } NSLog(@"veryitman--asyncRun. End Run."); } }
结合上面讲解的原理, 这里选择使用 runMode
方法来开启运行 RunLoop. 大家也可以自由发挥使用其他的方法.
注意: stopLoopRunning
是定义的一个属性.
移除的示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; // 取消线程 // 实际业务场景中自行决定 canCancel 的设置, 这里只是示例 BOOL canCancel = YES; if (canCancel) { [[self permanentThread] cancel]; } /// 停止常驻线程 { self.stopLoopRunning = YES; // 移除 port // 如果是用 timer 的方式的常驻线程, 可以 invalid 对应的 timer [self.threadRunLoop removePort:self.threadPort forMode:NSRunLoopCommonModes]; // 停止 RunLoop if (nil != self.threadRunLoop) { CFRunLoopStop([self.threadRunLoop getCFRunLoop]); } } }
离开页面后, 可以发现 NSLog(@"--- asyncRun ----");
停止了打印, 并且当前的 Controller 也 dealloc 了.
扫码关注,你我就各多一个朋友~