面试题:
autorelease
对象什么时候释放。
autorelease
的本质就是延迟调用 release
方法
在 MRC
的环境下,可以通过调用 [obj autorelease]
将对象添加到当前的 autoreleasepool
中,来延迟释放内存;
在 ARC
的环境下,当我们创建一个对象,可以通过 __autoreleasing
修饰符,会将对象添加到当前的 autoreleasepool
中,当 autoreleasepool
销毁时,会对 autoreleasepool
里面的所有对象做一次 release
操作。
注意:
- 编译器会检查方法名是否以
alloc
、new
、copy
、mutableCopy
开始,如果不是则自动将返回值的对象注册到autoreleasepool
中; - 以
__weak
修饰的对象,会注册到autoreleasepool
中。 - 调用
Foundation
对象的类方法(比如,[NSMutableDictionary dictionary]
、[NSArray array]
等)会注册到autoreleasepool
中。 id
的指针或对象的指针在没有显式地指定修饰符时候,会被默认附加上__autoreleasing
修饰符。
在没有手动加入 autoreleasepool
的情况下,autorelease
对象是在当前的 runloop
迭代结束时释放的,而它能够释放的原因是系统在每个 runloop
迭代中都加入了自动释放池 push
和 pop
。
当 autoreleasepool
销毁时,在调用堆栈中可以发现,系统调用了 -[NSAutoreleasePool release]
方法,这个方法最终通过调用 AutoreleasePoolPage::pop(void *)
函数来负责对 autoreleasepool
中的 autorelease
对象执行 release
操作。
AutoreleasePool 的实现原理
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@autoreleasepool
使用 clang -rewrite-objc
命令将下面的 Objective-C
代码重写成 C++
代码:
clang -rewrite-objc main.m
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;
};
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
}
声明一个 __AtAutoreleasePool
类型的局部变量 __autoreleasepool
来实现 @autoreleasepool {}
。当声明 __autoreleasepool
变量时,构造函数 __AtAutoreleasePool()
被调用,即执行:
atautoreleasepoolobj = objc_autoreleasePoolPush();
当出了当前作用域时,析构函数 ~__AtAutoreleasePool()
被调用,即执行:
objc_autoreleasePoolPop(atautoreleasepoolobj);
也就是说 @autoreleasepool {}
的实现代码可以进一步简化如下:
/* @autoreleasepool */ {
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
// 用户代码,所有接收到 autorelease 消息的对象会被添加到这个 autoreleasepool 中
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
因此,单个 autoreleasepool
的运行过程可以简单地理解为 objc_autoreleasePoolPush()
、[obj release]
和 objc_autoreleasePoolPop(void *)
三个过程。
AutoreleasePoolPage
image.png从图中可以看出
-
AutoreleasePoolPage
是由双向链表来实现的,parent
和child
就是用来构造双向链表的指针。 -
magic
用来校验AutoreleasePoolPage
的结构是否完整; -
AutoreleasePool
是按线程一一对应的,结构中的thread
指针指向当前线程。 -
AutoreleasePoolPage
会为每个对象会开辟4096
字节内存。 -
id *next
指向了下一个为空的内存地址(初始化为栈底),如果有添加进来的autorelease
对象,移动到下一个为空的内存地址中。
如果 AutoreleasePoolPage
里面的 autorelease
对象满了,也就是 id *next
指针指向了栈顶,会新建一个 AutoreleasePoolPage
对象,连接链表,后来添加的 autorelease
对象在新的 AutoreleasePoolPage
加入,id *next
指针指向新的 AutoreleasePoolPage
为空的内存地址,即栈底。所以,向一个对象发送 release
消息,就是将这个对象加入到当前 AutoreleasePoolPage
的 id *next
指针指向的位置。
POOL_SENTINEL(哨兵对象)
image.pngPOOL_SENTINEL
只是 nil
的别名。
在每个自动释放池初始化调用 objc_autoreleasePoolPush
的时候,都会把一个 POOL_SENTINEL push
到自动释放池的栈顶,并且返回这个 POOL_SENTINEL
哨兵对象。
而当方法 objc_autoreleasePoolPop
调用时,就会向自动释放池中的对象发送 release
消息,直到第一个 POOL_SENTINEL
。
objc_autoreleasePoolPush
objc_autoreleasePoolPush()
函数本质上就是调用的 AutoreleasePoolPage
的 push
函数。
void * objc_autoreleasePoolPush(void) {
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
根据源码得出,每次执行 objc_autoreleasePoolPush
其实就是创建了一个新的 autoreleasepool
,然后会把一个 POOL_SENTINEL
push
到自动释放池的栈顶,并且返回这个 POOL_SENTINEL
哨兵对象。
static inline void *push() {
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
push
函数通过调用 autoreleaseFast
函数并传入哨兵对象 POOL_SENTINEL
来执行具体的插入操作。
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
assert(page == hotPage());
assert(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
id *autoreleaseNoPage(id obj) {
// No pool in place.
assert(!hotPage());
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.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push an autorelease pool boundary if it wasn't already requested.
if (obj != POOL_SENTINEL) {
page->add(POOL_SENTINEL);
}
// Push the requested object.
return page->add(obj);
}
autoreleaseFast
函数在执行一个具体的插入操作时,分别对三种情况进行了不同的处理:
- 当前
hotPage
存在且没有满时,调用page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中。 - 当前
hotPage
存在且已满时,调用autoreleaseFullPage
初始化一个新的page
,调用page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中。 - 当前
hotPage
不存在时,调用autoreleaseNoPage
创建一个hotPage
,调用page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中。
objc_autoreleasePoolPop
objc_autoreleasePoolPop(void *)函数本质上也是调用的AutoreleasePoolPage的pop函数。
void objc_autoreleasePoolPop(void *ctxt) {
if (UseGC) return;
// fixme rdar://9167170
if (!ctxt) return;
AutoreleasePoolPage::pop(ctxt);
}
static inline void pop(void *token) {
AutoreleasePoolPage *page = pageForPointer(token);
id *stop = (id *)token;
page->releaseUntil(stop);
if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
} else if (page->child->child) {
page->child->child->kill();
}
}
}
pop
函数的入参就是 push
函数的返回值,也就是POOL_SENTINEL
的内存地址。根据这个内存地址找到所在的 AutoreleasePoolPage
然后使用 objc_release
释放 POOL_SENTINEL
指针之前的对象。
总结:
每调用一次 push
操作就会创建一个新的 autoreleasepool
,然后往 AutoreleasePoolPage
中插入一个 POOL_SENTINEL
,并且返回插入的 POOL_SENTINEL
的内存地址.
在执行 pop
操作的时候传入 POOL_SENTINEL
,根据传入的哨兵对象地址找到哨兵对象所处的 page
在当前AutoreleasePoolPage中,然后使用 objc_release
释放 POOL_SENTINEL
指针之前的对象,并把 id next
指针到正确位置。
网友评论