iOS深入原理

iOS - autoreleasepool 源码

2021-05-29  本文已影响0人  码代码的小马

AutoreleasePool:自动释放池是 Objective-C 开发中的一种自动内存回收管理的机制,为了替代开发人员手动管理内存,实质上是使用编译器在适当的位置插入release、autorelease等内存释放操作。当对象调用 autorelease 方法后会被放到自动释放池中延迟释放时机,当缓存池需要清除dealloc时,会向这些 Autoreleased 对象做 release 释放操作。

结论:
push时
(1). 当前page存在且没有满时,直接将对象添加到当前page中,即next指向的位置;
(2). 当前page存在并且已满时,创建一个新的page,并将对象添加到新创建的page 中,然后将这两个链表节点进行链接。
(3). 当前page不存在时,创建第一个page ,并将对象添加到新创建的page中。
pop时
这里的stop同样是POOL_BOUNDARY的地址
(1). 外部循环挨个遍历autoreleased 对象,直到遍历到哨兵对象POOL_BOUNDARY。
(2). 如果当前page没有 POOL_BOUNDARY,并且为空,则将hotPage设置为当前page的父节点。
(3). 给当前autoreleased对象发送release消息。
(4). 最后再次配置hotPage。

autoreleasepool与子线程

在NSThread退出了之后,与NSThread对应的AutoReleasePool也会被自动清空,所以当一个线程结束的时候,就会回收♻️AutoReleasePool中自动释放的对象。

每一个线程都会维护自己的AutoReleasePool,而每一个AutoReleasePool都会对应唯一一个线程,但是线程可以对应多个AutoReleasePool。

autoreleasepool与NSRunloop

子线程在使用autorelease对象的时候,会懒加载出来一个AutoreleasePoolPage,然后将对象插入进去。
那么问题又来了,autorelease对象在什么时候释放的呢?也就说AutoreleasePoolPage在什么时候调用了pop方法?
其实在上面创建一个NSThread的时候,在调用[NSthread exit]的时候,会释放当前资源,也就是把当前线程关联的autoReleasePool释放掉,而在这里当RunLoop执行完成退出的时候,也会执行pop方法,这就说明了为什么在子线程当中,我们没有显示的调用pop,它也能释放当前AutoreleasePool的资源的原因。

autoreleasepool与主线程

0x1代表的是kCFRunLoopEntry,也就是说第一个 Observer监视的事件是Entry(即将进入Loop时),其回调内会调用_objc_autoreleasePoolPush()创建一个自动释放池。其order优先级是-2147483647,优先级最高,保证创建自动释放池发生在其他所有回调之前。
0xa0对应的是kCFRunLoopBeforeWaiting和kCFRunLoopExit,也就是说第二个Observer监视了两个事件:kCFRunLoopBeforeWaiting准备进入休眠,kCFRunLoopExit即将退出RunLoop。在kCFRunLoopBeforeWaiting事件时调用 _objc_autoreleasePoolPop()和_objc_autoreleasePoolPush() 释放旧的自动释放池并创建新的自动释放池;在kCFRunLoopExit事件时调用_objc_autoreleasePoolPop() 来释放自动释放池,同时这个Observer的order优先级是 2147483647,优先级最低,保证其释放自动释放池的操作发生在其他所有回调之后。
所以在没有手动增加AutoreleasePool的情况下,Autorelease对象都是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池push和pop操作。
总结:

对于不同线程,应当创建自己的AutoReleasePool。如果应用长期存在,应该定期drain和创建新的AutoReleasePool,AutoReleasePool与RunLoop 与线程是一一对应的关系,AutoReleasePool在RunLoop在开始迭代时做push操作,在RunLoop休眠或者迭代结束时做pop操作。

源码

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_44_1ht3l6g55dv59_5s62wsv_bm0000gn_T_ViewController_3cfd72_mi_0);
    }
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;
};

每个AutoreleasePoolPage大小是4kb集4096字节

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct AutoreleasePoolEntry {
        uintptr_t ptr: 48;
        uintptr_t count: 16;

        static const uintptr_t maxCount = 65535; // 2^16 - 1
    };
    static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif

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

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

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

关于DebugPoolAllocation的定义

//翻译:当自动释放池无序弹出时停止,并允许堆调试器跟踪自动释放池
OPTION( DebugPoolAllocation,      OBJC_DEBUG_POOL_ALLOCATION,      "halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools")

    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(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);
        }
    }

hotPage

    static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }

autoreleaseFullPage

    static __attribute__((noinline))
    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);
    }
    static __attribute__((noinline))
    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", 
                         objc_thread_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);
    }

参考:
apple源码
iOS -- Autorelease & AutoreleasePool

上一篇下一篇

猜你喜欢

热点阅读