前言
Objective-C 对象的声明周期取决于其引用计数。释放对象有两种方式:
- 调用 release 方法,使其保留计数立即递减
- 调用 autorelease 方法,将其加入自动释放池中。清空自动释放池时,系统会向其中的对象发送 release 消息。
那么问题来了,自动释放池什么时候清空呢。下面我们详细介绍下 Autorelease pool。
autorelease 对象何时释放。
autorelease 对象会在 autoreleasepool 清空时收到 release 消息。
- 系统创建的线程中(如主线程和global queue),默认都有自动释放池,当前的 runloop 迭代结束时,会将其清空。
- 手动添加 autoreleasepool ,会在当前作用域大括号结束时清空。
手动添加 autoreleasepool
下面这段代码中,花括号定义了自动释放池的范围,自动释放池于左括号处创建,于右括号处自动清空。位于此范围内的对象,将在此范围末尾处收到 release 消息。
@autoreleasepool{
// ...
}
自动释放池可以嵌套,本例中的两个对象都是由类的工厂方法所创建,这样创建的对象会自动释放,NSString 类型的对象放在外围的释放池中,NSNumber 类型的对象则放在里层的释放池中。
@autoreleasepool{
NSString *string = [NSString stringWithFormat:@"1 = %i",1];
@autoreleasepool {
NSNumber *number = [NSNumber numberWithInt:1];
}
}
autoreleasepool 用法
一般情况下无须手动创建自动释放池。
系统会自动创建一些线程,比如主线程和GCD机制中的线程,这些线程默认都有自动释放池,每次开始一次新的事件循环时会创建自动释放池,事件循环(event loop)结束时,就会将其清空,所以,一般不需要自己创建自动释放池。
以下两种情况需要手动创建
- main 函数中。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
从技术角度看,不是非得有个自动释放池块。因为块的末尾刚好是应用程序的终止处,此时操作系统会把程序的全部内存释放掉。
但是,如果不写这个块的话,那么由 UIApplication 函数所自动释放的那些对象,就没有自动释放池可以容纳了。系统也会发出警告消息来表明这一情况。
- for 循环中创建大量对象时
如果 for 循环里创建大量临时对象,自动释放池要等下一次事件循环才会清空。这就意味着在执行for循环时,会持续有新对象创建出来,并加入释放池中。所有对象要等for循环执行完才会释放,会导致内存持续上涨。
NSArray *databaseRecords = /* ... */
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
@autoreleasepool {
WYJPerson *person = [WYJPerson alloc] initWithRecord: record];
[people addObject:person];
}
}
用自动释放池将循环中的语句包起来,那么循环中自动释放的对象会放在这个池,而不是主线程的自动释放池。这样,每次循环时都会建立并清空释放池。
Autorelease pool 实现原理
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. Thread-local storage points to the hot page, where newly autoreleased objects are stored.
上面这段是苹果官方对 autorelease pool 实现的一段描述。
大概意思是:每个线程的 autorelease pool 都是一个存放指针的栈。每个指针都指向一个需要释放的对象,或者是 autorelease pool 的一个边界。pool token 是一个指向释放池边界的指针。当释放池被弹出时,哨兵对象之后加入的对象都会被释放。栈是由page组合成双向链表,需要时添加或删除page。
AutoreleasePoolPage
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
这两个函数都是对AutoreleasePoolPage的简单封装。