iOS 题目详解 部分二

主要讲解子线程的保活方式, 以及 Autorelease 对象的释放时机

iOS 题目详解 部分一
iOS 题目详解 部分二
iOS 题目详解 部分三

iOS 题目简述 部分一

题目1. 子线程如何保活?

首先我们知道主线程中的Runloop是默认开启的, 而子线程中的Runloop是第一次获取的时候才会创建;另外Runloop中如果没有添加任何的Timers, Observers, PortsRunloop会立即退出;因为我们可以通过向子线程中添加如上的任意一种即可保活子线程;

  • Case1 : 首先看下正常用法:
#case1  执行完毕后线程就会立即销毁
XThread *thread1 = [[XThread alloc] initWithTarget:self selector:@selector(thread1Excute) object:nil];
[thread1 start];

- (void)thread1Excute {
    ///执行完毕后线程就销毁了
    NSLog(@"thread1执行 线程: %@", [NSThread currentThread]);
}

执行完成后打印结果如下, 单纯的子线程, 任务执行完毕, 退出线程, 线程销毁;

2020-09-03 14:58:26.742282+0800 RunloopMore1[2914:447091] thread1执行 线程: {number = 6, name = (null)}
2020-09-03 14:58:26.743149+0800 RunloopMore1[2914:447091] 线程销毁: -[XThread dealloc]
  • Case2 : 即使是被controller强引用, 线程内任务执行完毕不能再次执行其他任务, 最后跟着宿主对象一起销毁;
#子线程中默认没有开启runloop, 所以即使是被controller强引用, 
线程内任务执行完毕后也不能再次执行其他任务, 
类似僵尸对象跟着controller的销毁一起销毁;
    self.thread2 = [[XThread alloc] initWithBlock:^{
        NSLog(@"Thread2 执行");
    }];
    [self.thread2 start];

注意:[[XThread alloc] initWithTarget:self selector:@selector() object:nil];和方法
[self performSelector:@selector() onThread:self.thread2 withObject:nil waitUntilDone:NO];一旦执行后线程会对当控制器self造成强引用;
如果点击了下面执行此方法, 则会导致线程和controller都不能被释放, 因为有了循环引用;

- (IBAction)thread2Run:(id)sender {
    ///即使再次调用也没用, 因为子线程内没有开启runloop, 并且会造成循环应用导致都不能释放
    [self performSelector:@selector(thread2Excute) onThread:self.thread2 withObject:nil waitUntilDone:NO];
}
  • Case3 : 线程中添加Sources, Timers, Observers, Ports等保持线程存活;
    self.thread3 = [[XThread alloc] initWithBlock:^{
            /*
         我们知道runloop中如果没有任何source0/souce1/timer/observer 则runloop会立即退出;
         所以为runloop添加source1, 然后让runloop执行run;
         */
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init ] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }];
    [self.thread3 start];

这样的 话再次点击, 仍然可以执行任务

- (void)thread3Excute {
    NSLog(@"thread3执行 线程: %@", [NSThread currentThread]);
}
- (IBAction)thread3Run:(id)sender {
    [self performSelector:@selector(thread3Excute) onThread:self.thread3 withObject:nil waitUntilDone:NO];
}

关于Runlooprun方法

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.

大致意思为: 如果没有souces或者timers添加到runloop中则方法立即退出; 如果有,将在NSDefaultRunLoopMode模式下无限次执行runMode:beforeDate:来处理添加的soucestimers;
注意: 调用runlooprun方法后则不再能取消runloop, 即使是调用了CFRunLoopStop(CFRunLoopGetCurrent()) 也只是停了其中一次循环;
这种方式可以使线程保活, 但是同样有问题, 那就是无法停止Runloop进而导致线程无法销毁;

  • Case4 : 线程中添加Sources, Timers, Observers, Ports等保持线程存活;正确的使用方法如下:
     #不使用 runloop 的 run 方法;  自己通过runMode:beforeDate:方法来
     #控制 runloop 进而达到控制线程生命周期的目的;
    self.runloopStop = NO;
    self.thread4 = [[XThread alloc] initWithBlock:^{
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.runloopStop) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }];
    [self.thread4 start];

当要停止Runloop时执行如下代码

- (IBAction)thread4Kill:(id)sender {
    self.runloopStop = YES;
    [self performSelector:@selector(thread4Dealloc) onThread:self.thread4 withObject:nil waitUntilDone:YES];
    NSLog(@"如果上面waitUntilDone = YES, 则线程内方法执行完毕才能执行这里, waitUntilDone = NO, 则是并行执行;");
}


题目2. 子线程中 Autorelease对象什么时候释放的?

首先说下结论: Autorelease对象是通过AutoreleasePool管理, 跟着线程的退出而释放, 如果线程启用了Runloop保活, 则Autorelease一直不会被释放, 直到停止Runloop退出线程时才会释放;

下面分三种情况验证; 单纯的子线程, 子线程启用Runloop一直不销毁, 子线程启用Runloop后再次退出Runloop;
Case1: 单纯的子线程中的Autorelease对象, 通过Autorelease管理,跟着线程的销毁一起释放;验证代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.stopRunloop = NO;
    [self case0];
    NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}
- (void)case0 {
    self.thread0 = [[XThread alloc] initWithBlock:^{
        Model *model = [[[Model alloc] init] autorelease];
         NSLog(@"%@", [NSThread currentThread]);
    }];
    [self.thread0 start];
    [self.thread0 release];
}

打印结果如下:

2020-09-03 17:20:21.213622+0800 Test[3103:487120] -[ViewController3 viewDidLoad]
2020-09-03 17:20:21.213832+0800 Test[3103:487120] -[ViewController3 viewWillAppear:]
2020-09-03 17:20:21.214471+0800 Test[3103:487396] {number = 7, name = (null)}
2020-09-03 17:20:21.214687+0800 Test[3103:487396] -[Model dealloc]

Case2: 子线程通过启用Runloop一直保持存活, 则Autorelease对象也不会被销毁;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.stopRunloop = NO;
    [self case1];
    NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}
- (void)case1 {
    __block typeof(self) weakSelf = self;
    self.thread1 = [[XThread alloc] initWithBlock:^{
        Model *model = [[[Model alloc] init] autorelease];
         NSLog(@"%@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }];
    [self.thread1 start];
    [self.thread1 release];
}

打印结果为

2020-09-03 17:24:50.316906+0800 Test[3114:489698] -[ViewController3 viewDidLoad]
2020-09-03 17:24:50.317296+0800 Test[3114:489698] -[ViewController3 viewWillAppear:]
2020-09-03 17:24:50.318205+0800 Test[3114:489740] {number = 7, name = (null)}

即使是退出当前的控制器, 由于线程没有销毁则Autorelease也不会被销毁;

2020-09-03 17:27:35.287306+0800 Test[3114:489698] -[ViewController3 dealloc]

Case3: 子线程通过启用Runloop而后退出Runloop, 则Autorelease对象跟着线程的退出而销毁;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.stopRunloop = NO;
    [self case2];
    NSLog(@"%s", __func__);

}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}
- (void)case2 {
    __block typeof(self) weakSelf = self;
    self.thread2 = [[XThread alloc] initWithBlock:^{
        Model *model = [[[Model alloc] init] autorelease];
         NSLog(@"%@", [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopRunloop) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }];
    [self.thread2 start];
    [self.thread2 release];
}

打印结果为

2020-09-03 17:28:47.386353+0800 Test[3118:490612] -[ViewController3 viewDidLoad]
2020-09-03 17:28:47.386698+0800 Test[3118:490612] -[ViewController3 viewWillAppear:]
2020-09-03 17:28:47.387728+0800 Test[3118:490707] {number = 6, name = (null)}

然后我们点击屏幕, 停止当前线程的Runloop


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self performSelector:@selector(cancleRunloop) onThread:self.thread2 withObject:nil waitUntilDone:YES];
}

- (void)cancleRunloop {
    NSLog(@"%s", __func__);
    self.stopRunloop = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
}

打印结果如下, Autorelease跟着线程的退出而被释放;

2020-09-03 17:29:43.055258+0800 Test[3118:490707] -[ViewController3 cancleRunloop]
2020-09-03 17:29:43.055442+0800 Test[3118:490707] -[Model dealloc]

而后退出当前界面, 线程和当前控制器销毁;

2020-09-03 17:30:31.577495+0800 Test[3118:490612] -[XThread dealloc]
2020-09-03 17:30:31.577802+0800 Test[3118:490612] -[ViewController3 dealloc]


题目3. 主线程中 Autorelease 对象什么时候释放的?

结论是:

  • Autoreleasepool内的在autoreleasepool结束时就释放;
    如果没有显式的调用Autoreleasepool ,也是通过Autoreleasepool管理的, 其释放时机则如下:
  • ARC: 出了当前函数作用域就会被释放(创建的对象可以理解为系统帮开发者加了autorelease);
  • MRC:在当前Runloop周期结束后释放;

例如如下代码, model肯定是在autoreleasepool结束后就被释放了;

    @autoreleasepool {
      Model *model = [[[Model alloc] init] autorelease];
    }

那么如下代码局部变量model什么时候释放

- (void)viewDidLoad {
    [super viewDidLoad];
    Model *model = [[[Model alloc] init] autorelease];
    NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"%s", __func__);
}

打印结果如下;

2020-08-18 16:34:13.534459+0800 Topic2[8275:1405961] -[ViewController1 viewDidLoad]
2020-08-18 16:34:13.534614+0800 Topic2[8275:1405961] -[ViewController1 viewWillAppear:]
2020-08-18 16:34:13.575772+0800 Topic2[8275:1405961] -[Model dealloc]
2020-08-18 16:34:14.077758+0800 Topic2[8275:1405961] -[ViewController1 viewDidAppear:]

不论MRC还是ARCautorelease对象本质上都是通过autoreleasepool来管理的;
下面在MRC环境下验证它, 注意使用的iOS版本为10.0之前, 因为iOS10.0之后技术应该是有升级打印不直观;

- (void)viewDidLoad {
    [super viewDidLoad];
    Model *model = [[[Model alloc] init] autorelease];
    NSLog(@"Runloop : %@", [NSRunLoop mainRunLoop]);
    NSLog(@"%s", __func__);
    // Do any additional setup after loading the view, typically from a nib.
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}
#打印的Runloop的结果中添加的observer有如下:
    observers = {type = mutable-small, count = 6, values = (
    0 : {valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x107b06c4e), context = {type = mutable-small, count = 0, values = ()}}
    1 : {valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x107fe66ab), context = }
    2 : {valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x107b39a54), context = }
    3 : {valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10b6b4320), context = }
    4 : {valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x107b39a99), context = }
    5 : {valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x107b06c4e), context = {type = mutable-small, count = 0, values = ()}}

我们需要注意的是_wrapRunLoopWithAutoreleasePoolHandler这两个observer而他们对应Runloop状态实际分别为0x10xa0;
下面我们看下Runloop中的各个状态;

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    #进入Runloop   
    kCFRunLoopEntry = (1UL << 0),
    #即将处理Timer
    kCFRunLoopBeforeTimers = (1UL << 1),
    #即将处理Sources
    kCFRunLoopBeforeSources = (1UL << 2),
    #即将进入休眠 (1UL << 5 = 10 0000)
    kCFRunLoopBeforeWaiting = (1UL << 5),
    #即将唤醒休眠
    kCFRunLoopAfterWaiting = (1UL << 6),
    #即将退出Runloop 1UL << 7 = 1000 0000
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

kCFRunLoopEntry 就是Runloop即将开始; 1UL << 0 = 0x1
kCFRunLoopExit | kCFRunLoopBeforeWaiting 对应的就是Runloop即将休眠或者退出, 也就是当前Runloop周期的结束; (1UL << 5)|(1UL << 7) = 1010 0000 = 0xa0;
至此确定autorelease对象在MRC下当前Runloop周期进行销毁, 并且通过autoreleasepool管理;

另外关于Autorelease对象是通过AutoreleasePool管理的验证, 在其对象销毁时时候, 看下其函数调用栈也可得出结论, 具体可以看这篇文章


参考文章
子线程保活测试代码
iOS Runloop 补充
AutoreleasePool 的原理

你可能感兴趣的:(iOS 题目详解 部分二)