带你了解autroreleasePool的底层实现原理

1,144 阅读10分钟

一.探索前需知

1.1 什么是ARC与MRC?

相信大家早已对这个问题烂熟于心,但还是带大家一起过一下.

在MRC时代,系统判定一个对象是否销毁是根据这个对象的引用计数器来判断的.其中每个对象被创建时引用计数都为1,每当对象被其他指针引用时,需要手动使用[obj retain];让该对象引用计数+1,当指针变量不在使用这个对象的时候,需要手动释放release这个对象。 让其的引用计数-1,当一个对象的引用计数为0的时候,系统就会销毁这个对象.总的来说在MRC模式下必须遵循谁创建,谁释放,谁引用,谁管理

如果在MRC下使用ARC:
在Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入 -fobjc-arc

ARC自动内存管理:
WWDC2011和iOS5所引入自动管理机制——自动引用计数(ARC),它不是垃圾回收机制而是编译器的一种特性。ARC管理机制与MRC手动机制差不多,只是不再需要手动调用retain、release、autorelease;当你使用ARC时,编译器会在在适当位置插入release和autorelease;ARC时代引入了strong强引用来带代替retain,引入了weak弱引用.总结来说ARC是LLVM和Runtime配合的结果

在ARC下使用MRC方法:
在ARC工程中如果要使用MRC的需要在工程的Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入 -fno-objc-arc

1.2 autoreleasePool自动释放池

自动释放池始于MRC时代,主要是用于 自动 对 释放池内 对象 进行引用计数-1的操作,即自动执行release方法,在MRC中使用autoreleasepool必须在代码块内部手动为对象调用autorelease把对象加入到的自动释放池,系统会自动在代码块结束后,对加入自动释放池中的对象发送一个release消息.无需手动调用release.

二 .autoreleasePool的初探

2.1 autoreleasePool的创建

我们在创建工程的时候默认创建的.m文件main函数有个autoreleasePool的创建,而我们自己的代码中也可以创建autoreleasePool 对象:

main函数里的:

#import <UIKit/UIKit.h>

#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

 自己代码中的:

- (void)testConst{
    
    @autoreleasepool {
        
        for (int i = 0; i<1000; i++) {
            NSString *str1 = [NSString stringWithFormat:@"%ld",i];
        }
    }
}

在这段代码里会创建大量的临时变量(先不考虑代码合不合理哈),就会消耗过多的内存空间,所以在开发过程中,如果当遇到需要创建、使用大量的临时变量时,可以将相关的代码放在autoreleasePool中进行,当出了@autoreleasepool {},这些临时变量便会自动的进行释放.

2.2 autoreleasePool的实现

我们想看下autoreleasePool的底层实现该如何?

一般我们想看系统底层实现,一般有两个途径:第一是LLVM源码分析 第二个就是clang-rewrite-objc 进行底层编译,现在我们先用第二种方式进行探究:

将.m文件 clang-rewrite-objc -o main.cpp

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        return UIApplicationMain(argc, argv,
                                 __null,
                                 NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")))
                                 );
    }
}

我们看到了@autoreleasepool {} 底层编译成 __AtAutoreleasePool __autoreleasepool.

我们一起来看下__AtAutoreleasePool结构

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
  ~__AtAutoreleasePool() {
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  void * atautoreleasepoolobj;
};

原来__AtAutoreleasePool 是个结构体,结构体里有:

__AtAutoreleasePool() {

     atautoreleasepoolobj = objc_autoreleasePoolPush();

}和

~__AtAutoreleasePool() {

   objc_autoreleasePoolPop(atautoreleasepoolobj);

}这两个函数,其实__AtAutoreleasePool(){}是构造函数,而~__AtAutoreleasePool() {}这个是析构函数,其实也好理解当@autoreleasepool {}刚创建的时候底层会对atautoreleasepoolobj进行构造,当出了@autoreleasepool {} 方法区域的时候,底层会对atautoreleasepoolobj进行析构pop出去.

但是objc_autoreleasePoolPush、objc_autoreleasePoolPop 到底做了什么事呢?我们只有从源码中找到答案了,请看下面的分析 autoreleasePool的进阶.

三 .autoreleasePool的进阶

想要了解底层,必须要有源码,首先我们在网上 http://www.opensource.apple.com/apsl/里下一份objc的源码通过一些配置导入我们的工程.(最新的版本好像是objc4-779.1版本)

3.1  objc_autoreleasePoolPush方法实现:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

AutoreleasePoolPage 又是什么东西呢?点进去看看:

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
.
.
.
}

原来AutoreleasePoolPage继承于AutoreleasePoolPageData, AutoreleasePoolPageData 结构如下:

struct AutoreleasePoolPageData
{
	magic_t const magic; // 16
	__unsafe_unretained id *next; //8
	pthread_t const thread; // 8
	AutoreleasePoolPage * const parent; //8
	AutoreleasePoolPage *child; //8
	uint32_t const depth; // 4
	uint32_t hiwat; // 4

	AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
		: magic(), next(_next), thread(_thread),
		  parent(_parent), child(nil),
		  depth(_depth), hiwat(_hiwat)
	{
	}
};

原来AutoreleasePoolPageData是个结构体,里面有自己的一些成员变量,如:magic_t的结构体变量、next指针类型变量、depth、hiwat变量、还有parent、child指针成员变量,

其中指针类型占8个字节,uint32_t类型占4个字节,结构体类型取决于结构体本身的大小,比如magic_t结构体 ,里面是:

static const uint32_t M0 = 0xA1A1A1A1;
#   define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];

有的盆友会说到 int32_t M0、size_t M1_len 各占4个字节,但是忽略了一点:

static const,静态变量.静态变量类型数据大小是放在内存里的全局数据段里,不在堆区,

所以magic_t结构体所占的大小是uint32_t m[4]的大小,一个uint32_t 为四字节,整个就是 4*4为16个字节.

所以AutoreleasePoolPageData结构体自己变量共占 56个字节.各个变量所占大小已在上面标注,其中各个变量所代表的意思如下:


总结一下AutoreleasePoolPage其实就是一个双向链表结构,AutoreleasePoolPage(自动释放池页) 用来存放 autorelease 的对象但是每一页的大小是有限制的,假如某个AutoreleasePoolPage页中需要存放的autorelease 的对象过多,一页存放不完,所以它就需要指向父结点点,在指向父结点里的AutoreleasePoolPage页中继续存放.

那么每一页大小时所少呢?

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
	friend struct thread_data_t;

public:
	static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
		PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
		PAGE_MIN_SIZE;  // size and alignment, power of 2
.
.
.
#endif
}

有个PAGE_MAX_SIZE,点击进去是4096.原来每一页AutoreleasePoolPage可以存放4096个字节.一共4096个字节, (4096 - AutoreleasePoolPage 中自己成员变量所占的字节)/每个对象中所占的字节.  (4096 - 56)/8 = 505. 好的,每一AutoreleasePoolPage可以存放505个对象.下面把AutoreleasePoolPage 结构图放在下面,仅供大家参考.

结构弄清楚之后,我们继续跟代码:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // 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;
    }

程序代码回来到else的判断里,  dest = autoreleaseFast(POOL_BOUNDARY);这个POOL_BOUNDARY 就相当于 AutoreleasePoolPage 里的边界,一些技术书籍里也称作是哨兵对象,这个对象很有用的,等下就来看苹果为何这么设计:

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);
        }
    }

之前通过分析代码一进入这个 @autoreleasepool {}时,就会调用objc_autoreleasePoolPush方法,接着来到了 autoreleaseFast 方法,AutoreleasePoolPage *page = hotPage();取的此时的page,因为第一次创建 page肯定取不到,所以会来到autoreleaseNoPage(obj)方法,

id *autoreleaseNoPage(id obj)
    {
        ASSERT(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            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", 
                         objc_thread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            return setEmptyPoolPlaceholder();
        }
        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        return page->add(obj);
    }

一步步执行代码发现 会来到:

      // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        return page->add(obj);

创建个new AutoreleasePoolPage,并且setHotPage,紧接着将我们的哨兵对象加入到AutoreleasePoolPage.也就是page->add(POOL_BOUNDARY);操作.

3.2 autorelease 方法实现

刚刚我们在3.1的时候说到,@autoreleasepool {} 会创建个new AutoreleasePoolPage,这个AutoreleasePoolPage 会将POOL_BOUNDARY 添加进来,那么什么时候会添加自动释放的对象呢?

肯定是当某个对象调用 autorelease方法 的时候,AutoreleasePoolPage 会把调用的对象加进来.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        NSObject *objc = [[NSObject alloc] autorelease];
        NSLog(@"objc = %@",objc);
}

回来到底层objc:

objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

objc_object::autorelease()
{
    ASSERT(!isTaggedPointer());
    if (fastpath(!ISA()->hasCustomRR())) {
        return rootAutorelease();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}

inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

objc_object::rootAutorelease2()
{
    ASSERT(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj)
    {
        ASSERT(obj);
        ASSERT(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

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);
        }
    }

终于来到这里了,AutoreleasePoolPage *page = hotPage(); 获取page,在3.1的时候page已经创建了,所以他回来到 if (page && !page->full()) ,看看本页(AutoreleasePoolPage)有没有满,没满的话直接添加 page->add(obj);如果满的话来到autoreleaseFullPage(obj, page);

id *autoreleaseFullPage(id obj, AutoreleasePoolPage *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);
    }

首先来到这里是个do-while 循环,while (page->full()); 这个方法就是因为之前的那个page满了才会来到这里,所以这个条件必然满足.然后进行判断 page是否有子节点了,如果有的话直接用子节点的page,如果没有的话那只能新创建个page了,然后新创建的page 父节点指向原来的节点.最后page->add(obj).

3.3  objc_autoreleasePoolPop(atautoreleasepoolobj)方法实现:

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            page = pageForPointer(token);
        }

        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 (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        return popPage<false>(token, page, stop);
    }

这里面:

stop = (id *)token;
 if (*stop != POOL_BOUNDARY) {
 }

进行判断,pop什么时候结束,肯定是自动释放池页pop到那个哨兵对象的时候才算全部释放完,所以*stop != POOL_BOUNDARY,除非是坏节点,要不然*stop == POOL_BOUNDARY,

紧接着popPage<false>(token, page, stop);

 popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

将自动释放池页 kill,包括子页child,父页parent 都进行kill.使所有的autorlease对象都进行释放.

四. 总结

  • 在APP中,整个主线程是运行在一个自动释放池中的。

  • main函数中的自动释放池的作用:这个池块给出了一个pop点来显式的告诉我们这里有一个释放点,如果你的main在初始化的过程中有别的内容可以放在这里。

  • 使用@autoreleasepool标记,调用push()方法。

  • 没有hotpage,调用(),设置EMPTY_POOL_PLACEHOLDER

  • 因为设置了EMPTY_POOL_PLACEHOLDER,所以会设置本页为hotpage,添加边界标记POOL_BOUNDARY,最后添加obj。

  • 继续有对象调用autorelease,此时已经有了page,调用page->add(obj)

  • 如果page满了,调用autoreleaseFullPage()创建新page,重复第6点。

  • 到达autoreleasePool边界,调用pop方法,通常情况下会释放掉POOL_BOUNDARY之后的所有对象


  •