最近和 bestswifter 、kuailejim 搞了一套模拟面试,然后不管是应届生还是工作两三年的高级工程师都对下面这几个问题比较懵逼,可能是开发中用到的不多,在这里浅浅的讨论下
- Autoreleasepool 与 Runloop 的关系
- ARC 下什么样的对象由 Autoreleasepool 管理
- 子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?
针对第一个问题,比较容易理解,可以看一下:ibireme 的 深入理解RunLoop,主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理
第二个问题,ARC 下什么样的对象由 Autoreleasepool 管理呢?大多数人的回答是:“都会由 pool 进行管理”。其实并不是这样的,对于普通的对象是由编译器在合适的地方为我们 Realease 了。针对这个问题,我已经总结过:引用计数带来的一次讨论,是参考了经典的《iOS与OS X多线程和内存管理 》这本书。
针对第三个问题,感觉比较难以回答,需要很细致的读过 Runtime 、Autoreleasepool 的源码才可以。我也是参考了 StackOverFlow 的回答:does NSThread create autoreleasepool automaticly now?。我再来简单阐述下,在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用 page->add(obj)
将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!StackOverFlow 的作者也说道,这个是 OS X 10.9+和 iOS 7+ 才加入的特性。并且苹果没有对应的官方文档阐述此事,但是你可以通过源码了解。这里张贴部分源代码:
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// No pool in place.
// hotPage 可以理解为当前正在使用的 AutoreleasePoolPage。
assert(!hotPage());
// POOL_SENTINEL 只是 nil 的别名
if (obj != POOL_SENTINEL && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
(void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
// Install the first page.
// 帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push an autorelease pool boundary if it wasn't already requested.
// POOL_SENTINEL 只是 nil 的别名,哨兵对象
if (obj != POOL_SENTINEL) {
page->add(POOL_SENTINEL);
}
// Push the requested object.
// 把对象添加到 自动释放池 进行管理
return page->add(obj);
}
网友评论
实际一个线程对应一个autoreleasepool堆栈,而这个堆栈则是通过AutoreleasepoolPage的双向链表(数据结构)进行实现的。所以对于第三个问题“子线程对Autorelease对象如何处理”,子线程中在第一次push的时候创建AutoreleasepoolPage双链表,也就是autoreleasepool堆栈,然后所有的autoreleasepool后者说Autorelease对象都被压入到栈里,只是每个autoreleasepool之间都有一个POOL_SENTINEL标记界限(栈层次)。而调用[obj autoreplease]或者@autoreleasepool{}都会发生push操作。
__weak id ref = nil;
@Implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSString *str;
@autoreleasepool {
{
NSString *str = [[NSString alloc] initWithFormat:@"aaa"];
ref = str;
}
NSLog(@"-.ref = %@(%p)", ref, ref);
NSString *str = [@[@"1", @"2", @"3", @"4"] componentsJoinedByString:@"|"];
// ref = str;
NSLog(@"0.ref = %@(%p)", ref, ref);
}
NSLog(@"1.ref = %@(%p)", ref, ref);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"2.ref = %@(%p)", ref, ref);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"3.ref = %@(%p)", ref, ref);
}
@EnD
```
这面这段代码的输出结果:
```
2017-09-29 14:37:19.073666+0800 SJAdditionsTest[1820:1188820] -.ref = aaa(0xa000000006161613)
2017-09-29 14:37:19.073804+0800 SJAdditionsTest[1820:1188820] 0.ref = aaa(0xa000000006161613)
2017-09-29 14:37:19.073898+0800 SJAdditionsTest[1820:1188820] 1.ref = aaa(0xa000000006161613)
2017-09-29 14:37:19.074108+0800 SJAdditionsTest[1820:1188820] 2.ref = aaa(0xa000000006161613)
2017-09-29 14:37:19.085965+0800 SJAdditionsTest[1820:1188820] 3.ref = aaa(0xa000000006161613)
```
和预期的完全不一样,大神能解释下吗?
POSIX thread APIs 方式创建的线程 不用添加autoreleasepool,cocoa框架创建的线程都会自动加上 @autoreleasepool