1. Autorelease简介
Autorelease是iOS开发的一种内存管理机制,用来延迟内存释放。系统在每个runloop中都加入了自动释放池的push和pop,Autorelease对象在当前的runloop迭代结束时释放,它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池的push和pop。
//autorelease方法
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
//rootAutorelease 方法
inline id objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
//检查是否可以优化
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
//放到auto release pool中。
return rootAutorelease2();
}
// rootAutorelease2
id objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
// 把一个对象放到auto release pool中,是调用了AutoreleasePoolPage::autorelease这个方法。
return AutoreleasePoolPage::autorelease((id)this);
}
public: static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
static inline id *autoreleaseFast(id obj)
{
// autorelease方法会把对象存储到AutoreleasePoolPage的链表里。
//等到auto release pool被释放的时候,把链表内存储的对象删除。
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
在iOS 程序启动之后,主线程会创建一个Runloop,也会创建两个Observer,回调工作都是在_wrapRunLoopWithAutoreleasePoolHandler()函数中。
第一个Observer监听的是Entry(即将进入Loop),回调是在_objc_autoreleasePoolPush()中创建自动释放池的,优先级是最高的,保证创建释放池是在所有回调之前。
第二个Observer监听有两个事件:BeforeWaiting(进入休眠)时调用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()释放旧的释放池以及创建新的释放池;Exit(退出Loop)调用_objc_autoreleasePoolPop()来释放自动释放池。这个优先级是最低的,保证释放池发生在所有回调之后调用。
AutoreleasePoolPage是依靠C++实现的类。
2. AutoreleasePoolPage
AutoreleasePool没有单独的结构,是由许多个AutoreleasePoolPage以双链表的方式组合而成的,parent指向是前一个page,而child指向的是下一个page。
AutoreleasePoolPage中的每个对象都会开辟出虚拟内存一页的大小(也就是4096个字节),除了实例变量占据空间,其他的空间都用来存储autorelease对象的地址。
id *next指向的是栈顶对象的下一个位置
如果AutoreleasePoolPage空间被占满时,会创建一个AutoreleasePoolPage连接链表,后来的对象也会在新的page加入。
双向链表的优点:
双向链表是每个节点有2个链接,一个是指向前一个节点(当此链接为第一个链接时,指向的是空值或空列表),另一个则指向后一个节点(当此链接为最后一个链接时,指向的是空值或空列表)。即一个是指向前一个节点的指针,另一个则指向后一个节点的指针。
单向链表适用于节点的增加删除,双向链表适用于需要双向查找节点值的情况。这即是AutoreleasePoolPage以双链表的方式组合的原因。缺点就是空间占用较单链表大。
假设当前线程只有一个AutoreleasePoolPage对象,对象的内存地址如下图:
当一个对象发送了autorelease消息,就是将当前这个对象加入到AutoreleasePoolPage的栈顶next指向的位置。
释放时刻:
每进行一次objc_autoreleasePoolPush调用时,runtime就会将当前的AutoreleasePoolPage加入一个哨兵对象,就会变成下面结构:
自动释放池是以一个个AutoreleasePoolPage组成,而AutoreleasePoolPage以双链表形成的自动释放池。pop的时候传入边界的对象,然后再对page中的对象发送release消息。
objc_autoreleasePoolPush返回值也就是哨兵对象的地址,被objc_autoreleasePoolPop作为参数。于是:
根据传入的哨兵位置找到哨兵所对应的page
将晚于哨兵对象插入的autorelease对象都发送一个release消息,并移动next指针到正确的位置
3. MRC和ARC下的不同
ARC与MRC的autorelease的使用,如下:
// MRC
NSAutoreleasePool *pool = [NSAutoreleasePool alloc] init];
id obj = [NSObject alloc] init];
[obj autorelease];
[pool drain];
// ARC
@autoreleasepool {
id obj = [NSObject alloc] init];
}
在ARC下,我们如果使用@autoreleasepool{}来创建一个AutoreleasePool,随后编译器将会改成下面:
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
4. 子线程中Autorelease的释放
1.子线程在使用autorelease对象时,如果没有autoreleasepool会在autoreleaseNoPage中懒加载一个出来。
2.在runloop的run:beforeDate,以及一些source的callback中,有autoreleasepool的push和pop操作,总结就是系统在很多地方都差不多autorelease的管理操作。
3.就算插入没有pop也没关系,在线程exit的时候会释放资源,执行AutoreleasePoolPage::tls_dealloc,在这里面会清空autoreleasepool。
__weak id obj;
...
[NSThread detachNewThreadSelector:@selector(createAndConfigObserverInSecondThread) toTarget:self withObject:nil];
- (void)createAndConfigObserverInSecondThread{
__autoreleasing id test = [NSObject new];
NSLog(@"obj = %@", test);
obj = test;
[[NSThread currentThread] setName:@"test runloop thread"];
NSLog(@"thread ending");
}
在obj = test处设置断点使用 watchpoint set variable obj命令观察obj,可以看到obj在释放时的方法调用栈是这样的。
通过这个调用栈可以看到释放的时机在_pthread_exit。然后执行到AutorelepoolPage的tls_dealloc方法。
thread在退出时会释放自身资源,这个操作就包含了销毁autoreleasepool,在tls_delloc中,执行了pop操作。
线程在销毁时会清空autoreleasepool。但是上述这个例子中的线程并没有加入runloop,只是一个一次性的线程。现在给这个线程加入runloop来看看效果会是怎么样的。
- (void)createAndConfigObserverInSecondaryThread{
[[NSThread currentThread] setName:@"test runloop thread"];
NSRunLoop *loop = [NSRunLoop currentRunLoop];
CFRunLoopObserverRef observer;
observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
kCFRunLoopAllActivities,
true, // repeat
0xFFFFFF, // after CATransaction(2000000)
YYRunLoopObserverCallBack, NULL);
CFRunLoopRef cfrunloop = [loop getCFRunLoop];
if (observer) {
CFRunLoopAddObserver(cfrunloop, observer, kCFRunLoopCommonModes);
CFRelease(observer);
}
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(testAction) userInfo:nil repeats:YES];
[loop run];
NSLog(@"thread ending");
}
- (void)testAction{
__autoreleasing id test = [NSObject new];
obj = test;
NSLog(@"obj = %@", obj);
}
在testAction()中加上watchpoint断点,观察obj的释放时机。
释放的时机在CFRunloopRunSpecific中,也就是runloop切换状态的时候。timer在自己的callback函数里插入了释放autorelesepool的代码。对于runloop,我们知道runloop一定要有source才能保证run起来以后不立即结束,而source有三种,custom source,port source,timer。即使是我们自定义的source,执行函数中没有释放autoreleasepool的操作也不用担心,系统在各个关键入口都给我们加了这些操作。