iOS机制

OC线程保活

2022-02-26  本文已影响0人  苍眸之宝宝

引入:

  什么是线程保活呢?为什么要保活?有什么用呢?上来就是灵魂三连,当然这也是这篇文章的主题;还包括在实现线程保活过程中遇到的问题的记录。

简介:

  线程:分主线程和其它线程(也叫子线程)。主线程中进行ui刷新和事件的处理等操作,且主线程存在RunLoop是一直存在线程中的,且在操作完成后不会退出RunLoop,而是处在闲置状态;而其它线程中的RunLoop是在第一次获取的时候创建并存在的,且当该线程的操作完成后,线程及其中的运行循环会自动销毁。
   线程的创建和销毁很耗性能,所以经常在子线程做事情最好使用线程保活,例如AFNetworking 2.X版本就使用RunLoop进行线程保活的。
  那么怎样才能让其它线程一直存活呢?
其实很简单,让该线程中RunLoop一直运行,不退出,该线程就会一直存活在程序中。
  那么另一个问题来了怎样保持RunLoop一直运行不退出呢?
RunLoop里面添加Source\Timer\ObserverPort相关的是Source事件,RunLoop运行起来。
  知易行难,在子线程保活的过程中会遇到各种小问题,下面是我在线程保活和释放过程中遇到的问题。

实现过程及遇到的问题:

1.子线程的正常创建和销毁:

  用NSThreadinitWithTarget: 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事件并运行RunLooprun方法,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.

它告诉我们,RunLooprun方法开启一个无限循环,导致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退出,线程销毁:

需要注意的点:

  1. 需要用标记,打破while循环;
  2. 如果用属性强引用引用线程,注意引用循环;
  3. 当外部强引用线程时,注意造成的循环引用;或者强引用线程的对象提前释放造成标记永远为NO,无法打破while,造成RunLoop无法退出;
  4. 当线程退出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两种封装
  1. RunLoop运行Port保活线程
    需要添加标记,打破while循环,退出RunLoop
  2. 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]
上一篇下一篇

猜你喜欢

热点阅读