上一篇中问题, 实现"常驻线程"的方案
上一篇"RunLoop 学习笔记"中是这么介绍常驻线程, 以及对应实现方法的:
即让子线程处于 "不消亡" 的状态, 一直在后台处理某些频发事件 / 等待其他线程发来消息
- 在子线程监控网络状态
- 在子线程开启一个定时器
- 在子线程长期监控其他行为
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run];
}
}
摘自 AFNetworking 源代码, AFN这样做的原理在于子线程下默认不开启 RunLoop, 需要手动开启, 而 RunLoop 不断跑圈需要满足以下条件之一 :
- RunLoop有事件源(输入源), 包含基于端口 (port) 的事件源 / custom 事件源等
- RunLoop存在定时器
因此, AFN为RunLoop的default模式增加了一个NSMachPort端口(实际上也可以是其他端口),也就相当于为RunLoop添加了事件源, 因此RunLoop可以不断的跑圈, 保证线程的不死状态
顺便提一下, AFN保持一个常驻线程的原因, 第一是因为子线程默认不会开启RunLoop, 它会像一个C语言程序一样运行完所有代码后退出线程, 而网络请求是异步的, 这就可能会出现通过网络请求获取到数据之后, 线程已经退出, 无法执行请求成功/失败的代理方法, 因此AFN开启了一个RunLoop, 保活了线程
这样做存在内存泄漏的问题
经过研究 bestswifter 的文章 深入研究 Runloop 与线程保活 的相关思想与结论, 对以前提出的方案做改进, 并鸣谢该作者
解决方案如下:
- (void)memoryTest {
for (int i = 0; i < 100000; ++i) {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
[self performSelector:@selector(stopThread) onThread:thread withObject:nil waitUntilDone:YES];
}
}
- (void)stopThread {
CFRunLoopStop(CFRunLoopGetCurrent());
NSThread *thread = [NSThread currentThread];
[thread cancel];
}
- (void)run {
@autoreleasepool {
NSLog(@"current thread = %@", [NSThread currentThread]);
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
if (!self.emptyPort) {
self.emptyPort = [NSMachPort port];
}
[runLoop addPort:self.emptyPort forMode:NSDefaultRunLoopMode];
[runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]];
}
}
文/bestswifter(简书作者)
原文链接:http://www.jianshu.com/p/10121d699c32
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
关于 RunLoop 线程保活技术更深层次的理解, 请移步至 bestswifter 的文章 深入研究 Runloop 与线程保活