引用计数
在Objective-C
内存管理中,每个对象都有属于自己的计数器;如果想让某个对象继续存活,就增加它的引用计数;当用完它之后,就减少该计数;当没人引用该对象,它的计数变为0之后,系统就把它销毁。
所以,在objective-C
的内存管理中,关键就在于对象释放的时机,autorelease
的妙处在于,它找到了一个合适的时机来释放返回对象,这个时机就是本次消息循环结束的时候。我们只需要在返回对象前,调用autorelease
,对象被加入autorelease pool
(但没有减少对象的引用计数,所以这时候返回的对象仍是有效的),然后返回,程序继续执行,直到完成本次消息循环之时,再把autorelease pool
中记录的临时对象一个个分别release
--减少引用计数。
AutoreleasePool
App启动后,苹果在主线程RunLoop
里注册了两个Observer
,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()
。
第一个Observer
监视的事件是Entry
(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush()
创建自动释放池。其order
是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个Observer
监视了两个事件: BeforeWaiting
(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit(即将退出Loop) 时调用_objc_autoreleasePoolPop()
来释放自动释放池。这个Observer
的order
是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被RunLoop
创建好的AutoreleasePool
环绕着,所以不会出现内存泄漏,开发者也不必显示创建Pool
了。
Autorelease原理
@autoreleasepool{ }
使用clang编译器把main.m
转化成main.cpp
文件,查看一下@autoreleasepool{ }
的C++
源码,可以看到@autoreleasepool{ }
其实很简单,是一个__AtAutoreleasePool
结构体,这个结构体的构造也很简单,一个构造函数和一个析构函数,构造函数中通过调用objc_autoreleasePoolPush()
函数来实现构造;析构函数通过调用objc_autoreleasePoolPop()
函数来实现析构。
OC
代码:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc]init];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
C++
代码:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
AutoreleasePoolPage
objc_autoreleasePoolPush()
和objc_autoreleasePoolPop()
的函数实现我们可以在苹果开源代码objc4的NSObject.mm
中找到,其实就是直接对AutoreleasePoolPage
的调用。
push
- AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
- AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
- AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
- 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
- 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
class AutoreleasePoolPage
{
magic_t const magic; // 用来校验 AutoreleasePoolPage 的结构是否完整
id *next; // 指向栈顶,也就是最新入栈的autorelease对象的下一个位置
pthread_t const thread; // 指向当前线程
AutoreleasePoolPage * const parent; // 指向父节点
AutoreleasePoolPage *child; // 指向子节点
uint32_t const depth; // 表示链表的深度,也就是链表节点的个数
uint32_t hiwat; // 表示high water mark(最高水位标记)
static inline void *push() {
id *dest;
if (DebugPoolAllocation) { // Debug模式
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage(); // 获取当前PoolPage
if (page && !page->full()) { // 当前Page存在且未满
return page->add(obj); // 把obj添加到当前Page中
} else if (page) { // 当前Page已满
return autoreleaseFullPage(obj, page);
} else { // 不存在PoolPage
return autoreleaseNoPage(obj);
}
}
}
pop
每当进行一次objc_autoreleasePoolPush
调用时,runtime
向当前的AutoreleasePoolPage
中add
进一个哨兵对象
,值为0(也就是个nil),那么这一个page就变成了下面的样子:
objc_autoreleasePoolPush
的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)
作为入参,于是:
- 根据传入的哨兵对象地址找到哨兵对象所处的
page
- 在当前
page
中,将晚于哨兵对象插入的所有autorelease
对象都发送一次- release
消息,并向回移动next
指针到正确位置- 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个
page
,直到哨兵所在的page
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // 是否占位池
// Popping the top-level placeholder pool.
// 弹出顶层占位池
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
// 使用了游泳池, 正常弹出内容
// 池仍像往常一样分配给重用
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
// 游泳池从未使用 清除占位符
setHotPage(nil);
}
return;
}
page = pageForPointer(token); // 通过哨兵地址获取哨兵对象所在page对象
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat(); // 记录最高水位标记
page->releaseUntil(stop); // 把哨兵对象之后入栈的对象进行出栈操作,并对出栈对象发出release消息
// memory: delete empty children
// // 删除空掉的page
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
// 在Debug期间进行删除操作
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
// 在不存在autorelease pools的情况删除空的page
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
// 如果当前page使用超过一半,就保留一个空的child page
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}