objc源码解析 | autorelease

AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。
在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

来学习下autorelease底层是如何实现的

本文基于objc4-750 点击下载

先来看下@autoreleasepool{} 参考链接

经过clang -rewrite-objc之后

/* @autoreleasepool */ {
    __AtAutoreleasePool __autoreleasepool; 
}

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

根据构造函数和析构函数的特点(自动局部变量的构造函数是在程序执行到声明这个对象的位置时调用的,而对应的析构函数是在程序执行到离开这个对象的作用域时调用),代码简化为

/* @autoreleasepool */ {
    void *atautoreleasepoolobj = objc_autoreleasePoolPush();
    ...
    objc_autoreleasePoolPop(atautoreleasepoolobj);
}

单个自动释放池的执行过程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)

来看下objc_autoreleasePoolPush

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

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

再来看下autorelease方法

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

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

    return rootAutorelease2();
}

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

prepareOptimizedReturn部分放到最后分析,autorelease同push方法一样最后也是调用autoreleaseFast

先来看下AutoreleasePoolPage结构

class AutoreleasePoolPage 
{
    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   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;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

    // SIZE-sizeof(*this) bytes of contents follow

    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
    static void operator delete(void * p) {
        return free(p);
    }

    inline void protect() {
#if PROTECT_AUTORELEASEPOOL
        mprotect(this, SIZE, PROT_READ);
        check();
#endif
    }

    inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
        check();
        mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
    }

    AutoreleasePoolPage(AutoreleasePoolPage *newParent) 
        : magic(), next(begin()), thread(pthread_self()),
          parent(newParent), child(nil), 
          depth(parent ? 1+parent->depth : 0), 
          hiwat(parent ? parent->hiwat : 0)
    { 
        if (parent) {
            parent->check();
            assert(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
        }
        protect();
    }

    ~AutoreleasePoolPage() 
    {
        check();
        unprotect();
        assert(empty());

        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        assert(!child);
    }

    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

    bool empty() {
        return next == begin();
    }

    bool full() { 
        return next == end();
    }

    bool lessThanHalfFull() {
        return (next - begin() < (end() - begin()) / 2);
    }

    ...
}

#define BYTE_SIZE       8       /* byte size in bits */
#define I386_PGBYTES    4096    /* bytes per 80386 page */

#define PAGE_SIZE       I386_PGBYTES
#define PAGE_MASK       (PAGE_SIZE - 1)

#define PAGE_MAX_SIZE   PAGE_SIZE

// This saves memory when the top level (i.e. libdispatch) pushes and pops pools but never uses them.
EMPTY_POOL_PLACEHOLDER 空池占位,当pool被push/pop但从来不使用时用于节约内存

POOL_BOUNDARY是一个边界对象nil, 原来的名称是POOL_SENTINEL哨兵对象, 用来标志每次push时边界

PAGE_MAX_SIZE = 4096 虚拟内存每个扇区4096个字节, 4K对齐

parent 父节点 指向前一个page
child 子节点 指向下一个page
可以看出这是以AutoreleasePoolPage为Node的双向链表结构

next指针指向将要add的位置

magic 检查校验完整性的变量
thread page当前所在的线程,AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
depth 链表的深度,节点个数
hiwat 即 high water mark 数据容纳的上限

begin(), 初始化之后返回POOL_BOUNDARY之后的位置
end()返回page的末端
next == begin()时,即只有POOL_BOUNDARY,empty
next == end()时, Page空间都被使用了, full

接着看autoreleaseFast

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

获取当前hotPage
当前hotPage存在,Page满时,加到当前hotPage,否则执行autoreleaseFullPage
当前hotPage不存在则执行autoreleaseNoPage 一个page都没有

来看下page->add

id *add(id obj)
{
    assert(!full());
    unprotect();
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;
    protect();
    return ret;
}

hotage满时

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

依次遍历hotPage的可用child,未找到时新创建一个page
重新设置hotPage,添加对象

来看下autoreleaseNoPage

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

    bool pushExtraBoundary = false;
    if (haveEmptyPoolPlaceholder()) {
        // We are pushing a second pool over the empty placeholder pool
        // or pushing the first object into the empty placeholder pool.
        // Before doing that, push a pool boundary on behalf of the pool 
        // that is currently represented by the empty placeholder.
        pushExtraBoundary = true;
    }
    else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
        // We are pushing an object with no pool in place, 
        // and no-pool debugging was requested by environment.
        _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();
    }

    // We are pushing an object or a non-placeholder'd pool.

    // 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时,当obj==POOL_BOUNDARY,执行setEmptyPoolPlaceholder

static inline id* setEmptyPoolPlaceholder()
{
    assert(tls_get_direct(key) == nil);
    tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
    return EMPTY_POOL_PLACEHOLDER;
}

static inline bool haveEmptyPoolPlaceholder()
{
    id *tls = (id *)tls_get_direct(key);
    return (tls == EMPTY_POOL_PLACEHOLDER);
}

static inline void *tls_get_direct(tls_key_t k) 
{ 
    assert(is_valid_direct_key(k));

    if (_pthread_has_direct_tsd()) {
        return _pthread_getspecific_direct(k);
    } else {
        return pthread_getspecific(k);
    }
}
static inline void tls_set_direct(tls_key_t k, void *value) 
{ 
    assert(is_valid_direct_key(k));

    if (_pthread_has_direct_tsd()) {
        _pthread_setspecific_direct(k, value);
    } else {
        pthread_setspecific(k, value);
    }
}

pthread_getpecific和pthread_setspecific同个线程中共享数据
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
AUTORELEASE_POOL_KEY的值保存在TLS(即Thread Local Storage)上

EMPTY_POOL_PLACEHOLDER前面提过为空池占位,即该线程在NoPage时的push操作并不会立即创建AutoreleasePoolPage对象,等到添加首个autorelease对象时方才创建该线程首个Page、设置hotPage、添加POOL_BOUNDARY、添加autorelease对象,其实就是按惰性的原则处理

来看下objc_autoreleasePoolPop

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

    // memory: delete empty children
    if (DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        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
        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();
        }
    }
}

void *pool = objc_autoreleasePoolPush();
...
objc_autoreleasePoolPop(pool);

当pop的 token == (void*)EMPTY_POOL_PLACEHOLDER,释放到最顶层coldPage()->begin()
page->releaseUntil(stop); //这里对stop进行了检查应为coldPage()->begin() 或者 指向的是POOL_BOUNDARY
releaseUntil从当前hotPage的next-1 一直释放到stop, 对其中的非POOL_BOUNDARY调用objc_release

来看下releaseUntil的具体实现

void releaseUntil(id *stop) 
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    
    while (this->next != stop) {
        // Restart from hotPage() every time, in case -release 
        // autoreleased more objects
        AutoreleasePoolPage *page = hotPage();

        // fixme I think this `while` can be `if`, but I can't prove it
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if (obj != POOL_BOUNDARY) {
            objc_release(obj);
        }
    }

    setHotPage(this);
}

// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
不做递归调用,当线程积累了大量垃圾时,有很多page的情况

static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
对当前hotpage进行一一释放,重置内存为0xA3A3A3A3,直到当前hotPage为空时,更新hotPage为parent节点继续释放对象

接着看之前pop最后的部分,忽略调试部分代码, 剩下为清理page对象

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

当前page有子节点并且 使用超过一半时保留一个空的子节点

来看下kill部分

void kill() 
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    AutoreleasePoolPage *page = this;
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        page = page->parent;
        if (page) {
            page->unprotect();
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}

循环遍历进行delete操作

关于NSAutoreleasePool

NSAutoreleasePool在MRC下使用
autorelease对象释放时机为pool release或者drain的时候
这部分没看到源代码,猜测是init的时候调用了push,release或drain调用了pop

关于RunLoop的部分

苹果在主线程RunLoop里注册了两个Observer:
第一个Observer监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其order是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个Observer监视了两个事件: BeforeWaiting(准备进入睡眠) 和 Exit(即将退出Loop),
BeforeWaiting(准备进入睡眠)时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

  • Entry -> BeforeWaiting 其间调用autorelease方法的对象自动释放的时机为BeforeWaiting
  • BeforeWaiting -> Exit 其间调用autorelease方法的对象自动释放的时机为Exit

关于autorelease调用优化部分

即前面遇到的代码prepareOptimizedReturn, 笔者通俗的理解就是对以下两种情况进行了优化

  1. ReturnAtPlus1情况,调用objc_autorelease(obj),紧接着调用objc_retain(obj),则这两次操作都直接返回obj ,如objc_retain(objc_autorelease(obj))

  2. ReturnAtPlus0情况,调用objc_retainAutoreleaseAndReturn(obj),紧接着调用objc_release(obj),则这两次操作都直接返回obj,如objc_release(objc_retainAutoreleaseAndReturn(obj))

  • objc_retainAutoreleaseAndReturn(obj) 等价于objc_autorelease(objc_retain(obj))

来看下实现,首先是准备优化

// Prepare a value at +1 for return through a +0 autoreleasing convention.
id objc_autoreleaseReturnValue(id obj)
{
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

    return objc_autorelease(obj);
}

// Prepare a value at +0 for return through a +0 autoreleasing convention.
id objc_retainAutoreleaseReturnValue(id obj)
{
    if (prepareOptimizedReturn(ReturnAtPlus0)) return obj;

    return objc_retainAutoreleaseAndReturn(obj);
}

// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool 
prepareOptimizedReturn(ReturnDisposition disposition)
{
    assert(getReturnDisposition() == ReturnAtPlus0);

    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}

// Try to accept an optimized return.
// Returns the disposition of the returned object (+0 or +1).
// An un-optimized return is +0.
static ALWAYS_INLINE ReturnDisposition 
acceptOptimizedReturn()
{
    ReturnDisposition disposition = getReturnDisposition();
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}

static ALWAYS_INLINE void 
setReturnDisposition(ReturnDisposition disposition)
{
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}

__builtin_return_address是一个内建函数,作用是返回函数的地址,参数是层级,如果是0,则表示是当前函数体返回地址;如果是1:则表示调用这个函数的外层函数。当我们知道了一个函数体的返回地址的时候,就可以根据反汇编,利用某些固定的偏移量,被调方可以定位到调用方放在返回值后面的汇编指令,来确定该条函数指令调用完成后下一个调用的函数

callerAcceptsOptimizedReturn(__builtin_return_address(0)) 检查Caller调用方是否接受优化
如果接受后将disposition值保存在TLS上

Caller调用方是否接受优化在arm64的实现

static ALWAYS_INLINE bool 
callerAcceptsOptimizedReturn(const void *ra)
{
    // fd 03 1d aa    mov fp, fp
    // arm64 instructions are well-aligned
    if (*(uint32_t *)ra == 0xaa1d03fd) {
        return true;
    }
    return false;
}

返回地址等于0xaa1d03fd接受优化, 这里的汇编指令mov fp, fp待研究

Tagged pointer也会进行优化

Tagged pointer objects do participate in the optimized return scheme, because it saves message sends. They are not entered in the autorelease pool in the unoptimized case.

Caller调用方接受 实现

// Try to accept an optimized return.
// Returns the disposition of the returned object (+0 or +1).
// An un-optimized return is +0.
static ALWAYS_INLINE ReturnDisposition 
acceptOptimizedReturn()
{
    ReturnDisposition disposition = getReturnDisposition();
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}

// Accept a value returned through a +0 autoreleasing convention for use at +1.
id
objc_retainAutoreleasedReturnValue(id obj)
{
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);
}

// Accept a value returned through a +0 autoreleasing convention for use at +0.
id
objc_unsafeClaimAutoreleasedReturnValue(id obj)
{
    if (acceptOptimizedReturn() == ReturnAtPlus0) return obj;

    return objc_releaseAndReturn(obj);
}

__attribute__((noinline))
static id 
objc_releaseAndReturn(id obj)
{
    objc_release(obj);
    return obj;
}

从TLS上取值,如果满足相应的情况则直接返回obj

小结,一个线程首次调用objc_autoreleasePoolPush创建一个空池占用, 添加首个autorelease对象时才创建首个Page
调用objc_autoreleasePoolPop(void *token)释放对象
此时token应为2种情况

  1. token == EMPTY_POOL_PLACEHOLDER 释放整个自动释放池
  2. token指向一个POOL_BOUNDARY,释放到该边界

你可能感兴趣的:(objc源码解析 | autorelease)