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执行 线程: <XThread: 0x281ce6340>{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] <XThread: 0x283a80c00>{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] <XThread: 0x280f29400>{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] <XThread: 0x2807ed780>{number = 6, name = (null)}

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


- (void)touchesBegan:(NSSet<UITouch *> *)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 = <CFArray 0x7fb0c870e840 [0x1079867b0]>{type = mutable-small, count = 6, values = (
    0 : <CFRunLoopObserver 0x7fb0c870e9e0 [0x1079867b0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x107b06c4e), context = <CFArray 0x7fb0c870e870 [0x1079867b0]>{type = mutable-small, count = 0, values = ()}}
    1 : <CFRunLoopObserver 0x7fb0c840c270 [0x1079867b0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x107fe66ab), context = <CFRunLoopObserver context 0x0>}
    2 : <CFRunLoopObserver 0x7fb0c870e740 [0x1079867b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x107b39a54), context = <CFRunLoopObserver context 0x7fb0ca002f30>}
    3 : <CFRunLoopObserver 0x7fb0c8407ab0 [0x1079867b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10b6b4320), context = <CFRunLoopObserver context 0x0>}
    4 : <CFRunLoopObserver 0x7fb0c870e8a0 [0x1079867b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x107b39a99), context = <CFRunLoopObserver context 0x7fb0ca002f30>}
    5 : <CFRunLoopObserver 0x7fb0c870ea80 [0x1079867b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x107b06c4e), context = <CFArray 0x7fb0c870e870 [0x1079867b0]>{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 的原理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341