在前面几篇文章,说了关于OC中的内存布局、内存管理方案、以及MRC状况下的retainCount、retain、release,可是MRC也已是过去式了,此次来讲说ARC。 从MRC到ARC的变化 就取决于@autoreleasepool。c++
@autoreleasepool 自动释放池: 管理内存的池,把不须要的对象放在自动释放池中,自动释放(延迟释放)这个池子内的对象。bash
@autoreleasepool的应用场景:函数
咱们能够在工程中随便找个地方打个断点 po [NSRunLoop currentRunLoop]
oop
也就是说AutoreleasePool建立是在一个RunLoop事件开始以前(push),AutoreleasePool释放是在一个RunLoop事件即将结束以前(pop)。 AutoreleasePool里的Autorelease对象的加入是在RunLoop事件中,AutoreleasePool里的Autorelease对象的释放是在AutoreleasePool释放时。 以上内容参考自这位老哥布局
使用编译器clang编译main.m转化成main.cpp文件(在终端:clang -rewrite-objc main.m)ui
$ cd main.m所在文件夹spa
$ clang -rewrite-objc main.m -o main.cpp命令行
会在原有路径下生成一个main.cpp,点开以后咱们能够找到以下代码:线程
__AtAutoreleasePool
,在当前代码直接搜索,很容易就能找到下面这些代码
注意:这里的~
是C++的析构函数(destructor) 与构造函数相反,当对象脱离其做用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。翻译
从上图咱们能够找到两个东西,从名字也很浅显,一个进池子一个出池子
objc_autoreleasePoolPush
objc_autoreleasePoolPop
结合上面的注意点,我以为能够先这么总结一下
@autoreleasepool,就是把在它做用域(就是"{}")中的代码,先push进去,而后等这些代码都干完活了,再把他们pop出去。
咱们先看一下这个 objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
复制代码
这里出现了一个AutoreleasePoolPage,且语法是C++的语法。咱们这里先看看这个AutoreleasePoolPage是何方神圣。
咱们先看看变量声明
class AutoreleasePoolPage
{
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // 4096字节 size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic; // 4*4字节 用来校验 AutoreleasePoolPage 的结构是否完整;
id *next; // 8字节 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() ;
pthread_t const thread; // 8字节 指向当前线程;
AutoreleasePoolPage * const parent; // 8字节,指向父结点,第一个结点的 parent 值为 nil ;双向链表上一个节点
AutoreleasePoolPage *child; //8字节, 指向子结点,最后一个结点的 child 值为 nil ;双向链表下一个节点
uint32_t const depth; // 4字节,表明深度,从 0 开始,日后递增 1;
uint32_t hiwat; // 4字节 , 表明 high water mark 。
//这几个加起来是56字节
...
}
复制代码
void
_objc_autoreleasePoolPrint(void);
复制代码
已知AutoreleasePoolPage size = 4096,自身占用56,那么剩余空间为4096-56=4040。 一个NSObject的大小为8字节,就是说一个AutoreleasePoolPage的剩余空间还能够容纳4040/8=505个NSObject。 因此就来一波这种操做
void
_objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0 ; i<505; i++) {
NSObject *obj = [[NSObject new]autorelease];
}
_objc_autoreleasePoolPrint();
}
return 0;
}
复制代码
而后看一下打印信息
中间省略几百个NSObject...
那么问题来了,按照咱们先前的计算,505个不是应该刚恰好装满一个PAGE吗?这第二页是什么鬼?明显就是多出了一个什么奇怪的东西。 咱们观察一下这三个地址
objc[32613]: [0x102002000] ................ PAGE (full) (cold) objc[32613]: [0x102002038] ################ POOL 0x102002038 objc[32613]: [0x102002040] 0x100f8d780 NSObject
前两个地址的区别就在于一个38,这里是16进制, 因此38在十进制中为 3*16+8 = 56,因此这一系列的#号为变量进入pool的起点 第三个地址就是从38变成了40,38要变成40,在16机制中就是须要38中的8再加上8,让3变成4。 因此就是这 0x102002038 里面的东西在做怪。那这个又是什么呢? 这里我先贴一下部分代码
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
// 翻译:pushExtraBoundary 推入一个额外边界
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
//当push一个新页或者第一页的时候,在push以前须要先push一个poolboundary(边界)
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// 异常状况,抛错
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
复制代码
这里解读一下这段代码 当咱们push一个对象进来时,没有page或者page满了,须要到新的一页的时候,他就会将pushExtraBoundary设置为true,在底下会判断pushExtraBoundary,若是为true,就会先push一个POOL_BOUNDARY进入page中。
因此这个POOL_BOUNDARY就是咱们上文中的“奇怪的东西”,从字面翻译咱们能够理解为这是一个“边界”或者叫“边界符”
这里给一张图,能够更好的理解,加深印象
这篇文章先到这里,下一篇autoreleasepool将会解析push跟pop