引入:
什么是线程保活呢?为什么要保活?有什么用呢?上来就是灵魂三连,当然这也是这篇文章的主题;还包括在实现线程保活过程中遇到的问题的记录。
简介:
线程:分主线程和其它线程(也叫子线程)。主线程中进行ui刷新和事件的处理等操作,且主线程存在RunLoop
是一直存在线程中的,且在操作完成后不会退出RunLoop
,而是处在闲置状态;而其它线程中的RunLoop
是在第一次获取的时候创建并存在的,且当该线程的操作完成后,线程及其中的运行循环会自动销毁。
线程的创建和销毁很耗性能,所以经常在子线程做事情最好使用线程保活,例如AFNetworking
2.X版本就使用RunLoop
进行线程保活的。
那么怎样才能让其它线程一直存活呢?
其实很简单,让该线程中RunLoop
一直运行,不退出,该线程就会一直存活在程序中。
那么另一个问题来了怎样保持RunLoop
一直运行不退出呢?
将RunLoop
里面添加Source\Timer\Observer
,Port
相关的是Source
事件,RunLoop
运行起来。
知易行难,在子线程保活的过程中会遇到各种小问题,下面是我在线程保活和释放过程中遇到的问题。
实现过程及遇到的问题:
1.子线程的正常创建和销毁:
用NSThread
的initWithTarget: selector: object:
或者initWithBlock:
方法创建线程,并start
。当线程中的操作完成后,线程和RunLoop会自动销毁。
/// 自定义线程,重写线程的dealloc辅助打印释放线程的信息
@implementation ZBYThread
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
/// 创建线程
- (void)threadNoRun {
ZBYThread * thread = [[ZBYThread alloc] initWithTarget:self selector:@selector(run2) object:nil];
[thread start];
}
/// run方法:运行结束后线程被释放
- (void)run {
@autoreleasepool { // 防止大次循环内存暴涨
for (int i = 0; i < 100; i++) {
NSLog(@"--子线程操作%ld",(long)i);
}
}
NSLog(@"%s ----end----", __func__);
}
上述代码运行的结果是任务打印任务结束后,直接输出end
并释放线程。如下:
-[ZBYThreadAliveViewController run] ----end----
-[ZBYThread dealloc]
2.只添加Port等事件,不运行RunLoop,情况和1相同:
代码:
- (void)threadNoRun {
ZBYThread * thread = [[ZBYThread alloc] initWithTarget:self selector:@selector(run2) object:nil];
[thread start];
}
- (void)run2 {
// addPort可以添加
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
// [[NSRunLoop currentRunLoop] addTimer:<#(nonnull NSTimer *)#> forMode:<#(nonnull NSRunLoopMode)#>]
@autoreleasepool { // 防止大次循环内存暴涨
for (int i = 0; i < 100; i++) {
NSLog(@"--子线程操作%ld",(long)i);
}
}
NSLog(@"--end--%@",[NSThread currentThread]);
}
打印结果:
--end--<ZBYThread: 0x6000005595c0>{number = 9, name = (null)}
-[ZBYThread dealloc]
3.添加Port
事件并运行RunLoop
的run
方法,RunLoop
一直运行,不会退出,导致线程不会销毁:
我们看一下run
方法的官方注视:
this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
// 这个方法有效地开始了一个无限循环,处理来自运行循环的输入源和计时器的数据。
If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:
// 如果您希望终止运行循环,则不应使用此方法。相反,可以使用其他运行方法之一,并在循环中检查您自己的其他任意条件。一个简单的例子是:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
where shouldKeepRunning is set to NO somewhere else in the program.
它告诉我们,RunLoop
的run
方法开启一个无限循环,导致RunLoop
及其所在的线程无法退出和销毁。
如果需要对RunLoop
进行退出操作,请使用runMode
方法。
代码:
- (void)threadRun {
ZBYThread * thread = [[ZBYThread alloc] initWithTarget:self selector:@selector(run3) object:nil];
[thread start];
dispatch_after(1.5, dispatch_get_main_queue(), ^{ // 延迟在子线程中添加操作
[self performSelector:@selector(doSomethingInAliveThread) onThread:thread withObject:nil waitUntilDone:NO];
});
dispatch_after(2.5, dispatch_get_main_queue(), ^{ // 退出线程,无法正常退出,因为NSRunLoop的run方法
[self performSelector:@selector(quitRunLoop) onThread:thread withObject:nil waitUntilDone:NO];
});
}
- (void)run3 {
@autoreleasepool { // 防止大次循环内存暴涨
for (int i = 0; i < 100; i++) {
NSLog(@"--子线程操作%ld",(long)i);
}
// addPort可以添加
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
// NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
[[NSRunLoop currentRunLoop] run];
NSLog(@"--end--%@",[NSThread currentThread]); // 当RunLoop停止时,会执行该代码
}
}
- (void)doSomethingInAliveThread {
// 任务可以正常执行,说明线程一直是活着的
NSLog(@"%s", __func__);
}
打印:
任务打印省略
-[ZBYThreadAliveViewController doSomethingInAliveThread]
最后没有线程释放的打印,说明线程一直存活。
4.添加Port事件并运行runMode方法,RunLoop一直运行,当标记改变RunLoop退出,线程销毁:
需要注意的点:
- 需要用标记,打破while循环;
- 如果用属性强引用引用线程,注意引用循环;
- 当外部强引用线程时,注意造成的循环引用;或者强引用线程的对象提前释放造成标记永远为NO,无法打破while,造成RunLoop无法退出;
- 当线程退出RunLoop后,无法重新启用。
代码:
- (void)threadRunModelStrong {
ZBYThread * thread = [[ZBYThread alloc] initWithTarget:self selector:@selector(runForStrong) object:nil];
[thread start];
// 属性强引用线程,容易造成循环引用
self.thread = thread;
dispatch_after(1.5, dispatch_get_main_queue(), ^{ // 延迟在子线程中添加操作
[self performSelector:@selector(doSomethingInAliveThread) onThread:thread withObject:nil waitUntilDone:NO];
});
dispatch_after(2.5, dispatch_get_main_queue(), ^{ // 退出线程,可以正常退出,因为NSRunLoop的runModel方法
[self performSelector:@selector(quitRunLoop2) onThread:thread withObject:nil waitUntilDone:NO];
});
}
- (void)runForStrong {
__weak typeof(self) weakSelf = self;
self.isStoped = NO;
@autoreleasepool {
for (int i = 0; i < 100; i++) {
NSLog(@"----子线程任务 %ld",(long)i);
}
NSLog(@"%@----子线程任务结束",[NSThread currentThread]);
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 往RunLoop里面添加Source\Timer\Observer,Port相关的事件
// 添加了一个Source1,但是这个Source1也没啥事,所以线程在这里就休眠了,不会往下走,----end----一直不会打印
[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
// weakSelf判断是否存在,不存在会默认为NO,无法打破while循环
// 要使用weakself,不然self强引用thread,thread强引用block,block强引用self,产生循环引用。
while (weakSelf && !weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%s ----end----", __func__); // 当RunLoop停止时,会执行该代码
}
}
/// 需要在quitRunLoop中,进行如下设置
- (void)quitRunLoop2 {
self.isStoped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
[self.thread cancel];
// 解决循环引用问题
self.thread = nil;
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
打印:
省略操作打印
-[ZBYThreadAliveViewController doSomethingInAliveThread]
-[ZBYThreadAliveViewController quitRunLoop2] <ZBYThread: 0x600000559240>{number = 12, name = (null)}
----end----
-[ZBYThread dealloc]
最后成功释放了线程。
6.完整封装见类:ZBYThreadTool两种封装
- RunLoop运行Port保活线程
需要添加标记,打破while循环,退出RunLoop - RunLoop运行source保活线程
更偏向底层c语言,控制的更精准;可以控制执行完source后不退出当前loop,这样不用写while循环添加标记了。
封装代码:
// .h代码
#import "ZBYThread.h"
/// 任务的回调
typedef void (^ZBYThreadToolTask)(void);
@interface ZBYThreadAliveTool : NSObject
@property (nonatomic, strong, readonly) ZBYThread * innerThread;
@property (nonatomic, assign, getter = isStopped) BOOL stopped;
/// 保活线程
+ (instancetype)threadToolForPort;
/// 保活线程
+ (instancetype)threadToolForC;
/// 在当前子线程添加一个执行任务
/// @param task 执行任务
- (void)addExecuteTask:(ZBYThreadToolTask)task;
/// 结束并销毁线程
- (void)stop;
@end
// .m代码
@interface ZBYThreadAliveTool ()
@property (strong, nonatomic, readwrite) ZBYThread * innerThread;
@end
@implementation ZBYThreadAliveTool
/// RunLoop运行Port保活线程
+ (instancetype)threadToolForPort {
ZBYThreadAliveTool * tool = [ZBYThreadAliveTool new];
tool.stopped = NO;
__weak typeof(tool) weakTool = tool;
tool.innerThread = [[ZBYThread alloc] initWithBlock:^{
NSLog(@"begin----");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakTool && !weakTool.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"end----");
}];
// 自动开始线程
[tool.innerThread start];
return tool;
}
/// RunLoop运行source保活线程
/// C语言方式和OC方式达到的效果都是一样的,但是C语言方式控制的更精准,可以控制执行完source后不退出当前loop,
/// 这样就不用写while循环了。
+ (instancetype)threadToolForC {
ZBYThreadAliveTool * tool = [ZBYThreadAliveTool new];
tool.stopped = NO;
// __weak typeof(tool) weakTool = tool;
tool.innerThread = [[ZBYThread alloc] initWithBlock:^{
NSLog(@"begin----");
// 创建上下文(要初始化一下结构体,否则结构体里面有可能是垃圾数据)
CFRunLoopSourceContext context = {0};
// 创建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 往Runloop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 销毁source
CFRelease(source);
// 启动
//参数:模式,过时时间(1.0e10一个很大的值),是否执行完source后就会退出当前loop
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
//如果使用的是C语言的方式就可以通过最后一个参数让执行完source之后不退出当前Loop,所以就可以不用stopped属性了
// while (weakSelf && !weakSelf.isStopped) {
// // 第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
// CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
// }
NSLog(@"end----");
}];
// 自动开始线程
[tool.innerThread start];
return tool;
}
- (void)addExecuteTask:(ZBYThreadToolTask)task {
if (!self.innerThread || !task) return;
[self performSelector:@selector(_executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop {
if (!self.innerThread) return;
[self performSelector:@selector(_stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
/// 对象释放,销毁其保活的线程
- (void)dealloc {
[self stop];
}
// MARK: - private methods
- (void)_stop {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)_executeTask:(ZBYThreadToolTask)task {
task();
}
@end
调用代码:
-(void)threadAliveTool {
ZBYThreadAliveTool * tool = [ZBYThreadAliveTool threadToolForPort];
// ZBYThreadAliveTool * tool = [ZBYThreadAliveTool threadToolForC];
[tool addExecuteTask:^{
NSLog(@"--添加操作--%@",[NSThread currentThread]);
}];
dispatch_after(1.0, dispatch_get_main_queue(), ^{
[tool stop];
});
}
打印:
begin----
--添加操作--<ZBYThread: 0x6000005a0f40>{number = 13, name = (null)}
end----
-[ZBYThread dealloc]
网友评论