主要讲解子线程的保活方式, 以及 Autorelease 对象的释放时机
iOS 题目详解 部分一
iOS 题目详解 部分二
iOS 题目详解 部分三
iOS 题目简述 部分一
题目1. 子线程如何保活?
首先我们知道主线程中的Runloop
是默认开启的, 而子线程中的Runloop
是第一次获取的时候才会创建;另外Runloop
中如果没有添加任何的Timers
, Observers
, Ports
则Runloop
会立即退出;因为我们可以通过向子线程中添加如上的任意一种即可保活子线程;
- 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];
}
关于Runloop
的run
方法
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:
来处理添加的souces
和timers
;
注意: 调用runloop
的run
方法后则不再能取消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
还是ARC
下autorelease
对象本质上都是通过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
状态实际分别为0x1
和0xa0
;
下面我们看下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 的原理