Objective-C内存篇(三) - Autorelease
什么是自动释放池
自动释放,也是延迟释放。
自动释放池的实现原理或者说作用:在自动释放池被销毁或耗尽时,会向池中的所有对象发送release消息,释放所有autorelease对象。
自动释放池中的使用
//MRC下
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; //相当于[obj release];
//ARC下
@autoreleasepool{
NSArray __autorelasing * arr = [[NSArray alloc] init];
或者
NSArray * arr = [NSArray arrayWithObject:@""]; // alloc/new/copy/mutableCopy之外的方法,生成的对象,默认是自动释放池管理(MRC与ARC下)
}
Autorelease Pool的实现原理
# NSAutoreleasePool对应的AutoreleasePoolPage类 - 相关代码
ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后clang -rewrite-objc
可编译成下面代码:
//被编译的代码
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool{
id obj = [[NSObject alloc] init];
}
return 0;
}
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
//略作改动,对应MRC下的代码
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc) init];/*等同于objc autoreleasePoolPush ( ) */
id obj = [[NSObject alloc) init];
[obi autorelease];/*等同于objc autorelease (obi ) */
[pool drain];/*等同于obic autoreleasePoolPop (pool ) */
而这几个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类
。
可通过objc4库的runtime/objc-arr.mm来确认苹果中autorelease的实现。
objo4/runtime/objc-arr.mm class AutoreleasePoolPage:
class AutoreleasePoolPage
{
static inline void *push (){ //相当于生成或持有NSAutoreleasePool类对象;
}
static inline void *pop (void *token){ //相当于废弃NSAutoreleasePool类对象;
releaseAll();
}
static inline id autorelease (id obj){ //相当于NSAutoreleasePoo1类的addobject类方法
AutoreleasePoolPage *autoreleasePoolPage =取得正在使用的AutoreleasePoolPage实例;
autoreleasePoolPage->add (obj );
}
id *add (id obj){//将对象追加到内部数组中;
}
void releaseAll (){//调用内部数组中对象的release实例方法;
}
};
void *objc autoreleasePoolPush (void){
return AutoreleasePoolPage: :push ();
}
void objc autoreleasePoolPop (void *ctxt){
AutoreleasePoolPage: :pop (ctxt);
}
id *objc autorelease (id obj){
return AutoreleasePoolPage: :autorelease (obj);
}
# AutoreleasePoolPage类
AutoreleasePoolPage是一个C++实现的类
AutoreleasePoolPage
- AutoreleasePool并没有单独的结构,而是由若干个
AutoreleasePoolPage作为结点
以双向链表
的形式组合而成,会在一个Page空间占满时进行增加,objc_autoreleasePoolPop(哨兵对象)的时候进行删除
- AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址、以及
哨兵对象的地址(见下文 释放时机 部分)
参数解读:
- parent 指向父结点,第一个结点的 parent 值为 nil ;
- child 指向子结点,最后一个结点的 child 值为 nil ;
- thread指针指向当前线程,每个AutoreleasePool只对应一个线程
-
id *next
指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置,初始化时指向begin(); - magic 用来校验 AutoreleasePoolPage 的结构是否完整
- depth 代表深度,从 0 开始,往后递增 1;
一个AutoreleasePoolPage的空间被占满时(next == end()时),会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
# 释放机制
每当
进行一次objc_autoreleasePoolPush
调用时,runtime就向当前的AutoreleasePoolPage中add进一个哨兵对象
,值为0(也就是个nil),那么这一个page就变成了下面的样子:
objc_autoreleasePoolPush
的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)
作为入参,于是:
- 根据传入的哨兵对象地址找到哨兵对象所处的page
- 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次
- release
消息,并向回移动next
指针到正确位置 - 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page
# 嵌套的AutoreleasePool
pop的时候总会释放到上次push的位置(上次push时返回的哨兵对象地址
)为止,多层的pool就是多个哨兵对象而已,互不影响。
__autoreleasing所有权修饰符
ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法
- id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰符,
赋值给对象指针时,所有权修饰符必须一致
- 只能自动变量,才可以显式指定__autoreleasing修饰符(包括局部变量、函数、方法参数)
加入自动释放池的几种方法
- 调用autorelease的对象(MRC下)、用__autoreleasing修饰的对象(ARC下)
- 用alloc/new/copy/mutableCopy之外的方法,生成的对象,默认是自动释放池管理(MRC与ARC下)
NSArray * array = [NSArray arrayWithCapacity: 1];
等同于
NSArray * array = [[[NSArray alloc] initWithCapacity:1] autorelease];
- __weak的变量指向一个__strong的对象,每次使用这个变量的时候,都会把这个变量加入到自动释放池中一次(ARC下)
因为_weak 修饰符只持有对象的弱引用,而且在访问引用对象的过程中,该对象可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保对象的存在。
# 打印自动释放池中的对象
可通过NSAutoreleasePool类中的调试用非公开类方法showPools来确认已被autorelease的对象的状况。showPools会将现在的NSAutoreleasePool的状况输出到控制台。
[NSAutoreleasePool showPools];
或者直接使用_objc_autoreleasePoolPrint()函数来打印(无论ARC是否有效
)
/* 函数声明 */
extern _objc_autoreleasePoolPrint();
/* 调用 */
_objc_autoreleasePoolPrint();
自动释放池的释放时机
- 主线程中的最外层@autoreleasepool {} :
runloop默认开启,每一次运行循环开始,也就是每当事件被触发时都会创建自动释放池。运行循环结束前会释放自动释放池,还有池子满了也会销毁。(无论ARC是否有效,NSRunloop都能随时释放注册到autoreleasepool中的对象
) - 子线程中的最外层@autoreleasepool {} :
runloop默认不开启,不会自动创建自动释放池,在需要使用自动释放池的时候,需要我们手动创建、添加自动释放池,此时如果所有的异步代码都写在自动释放池中,也可以理解为当子线程销毁的时候,自动释放池释放
- 线程中在一些代码场景中,自己创建的自动释放池,比如
1. 生成大量的临时变量
2. 生成大容量对象
1. UIImage转NSData : UIImageJPEGRepresentation / UIImagePNGRepresentation 这两个方法在转为NSData的时候,这些Data都会写到内存中,如果图片太多,太大,就会导致内存暴涨
2. [UIImage imageNamed: ] 会读入内存,所以相对的,速度也是最快的,Interface Builder(sb,xib)就是通过这个方法来加载的,图片被缓存,导致内存过大
以上这些,都需要我们及时清理对象、内存,避免造成内存占用过高
for (int i = 0; i < 10000; ++i) {
@autoreleasepool{
NSString *str = @"Hello World";
str = [str stringByAppendingFormat:@"- %d",i];
} //此时,自动释放池的释放时机就是在此处:大括号完成的时候
}
注意:使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
参考链接:
http://blog.sunnyxx.com/2014/10/15/behind-autorelease
http://www.cocoachina.com/ios/20150610/12093.html
参考书籍:《Objective-C 高级编程》