面试题iOS-Autorelease Pool

iOS - 内存管理(二)autoreleasepool

2020-10-13  本文已影响0人  FireStroy

自动释放池

先看一份关于autoreleasepool的编译代码。

int main(int argc, const char * argv[]) {
    @autoreleasepool {        
    }
    return 0;
}

int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool; }
    return 0;
}

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

在oc中autoreleasepool在经过clang的编译之后,底层的实现是一个__AtAutoreleasePool结构体,它拥有一个构造函数和一个析构函数。
很明显,在main()函数中,被自动释放池所包含的区域被当做为一块局部的代码块,进入代码块调用autoreleasepool的构造函数,离开作用域时会调用析构函数释放局部变量。

AutoreleasePoolPageData

上面的构造函数和析构函数都是来自于AutoreleasePoolPage这个结构体内,而AutoreleasePoolPage又是继承自AutoreleasePoolPageData那么看看它的内部源码:

/***********************************************************************
   Autorelease pool implementation

   A thread's autorelease pool is a stack of pointers. 
   Each pointer is either an object to release, or POOL_BOUNDARY which is 
     an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When 
     the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added 
     and deleted as necessary. 
   Thread-local storage points to the hot page, where newly autoreleased 
     objects are stored. 
**********************************************************************/
struct AutoreleasePoolPageData
{
    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)
    {
    }
};

AutoreleasePoolPage究竟是什么?结合源码给出的注释它有以下几个特点:

AutoreleasePoolPage的初始化

AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),
                                objc_thread_self(),
                                newParent,
                                newParent ? 1+newParent->depth : 0,
                                newParent ? newParent->hiwat : 0){
}

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

自动释放池初始化的时候

也就是说,autorelease对象内部管理的变量是放在栈结构中,栈顶初始化位置是begin()也就是结构体的末尾位置,也叫边界。每一次压栈,栈指针往高位移动8位。
每一个autorelease对象管理的栈节点叫做page,每一页的大小是4096个字节,autorelease对象自身成员变量占用56个字节外,余下空间可以压入(4096-56)/8 = 505个对象指针,(除第一页有一个特殊标记只有504个外,objc779模拟器环境)。
最后一页会标记为hotPage(),其他页标记为coldPage()

autorelease push和pop

对象是如何进入自动释放池的栈队列中?
AutoreleasePoolPage压入一个对象指针基本流程:

  1. 判断page是否存在,是否满了。
  2. page不存在,新创建一个page,并依据节点关系初始化。
  3. 如果page满了就会新建下一页。
  4. 如果page没满,就把*next中存入压入的对象指针,并且next++
      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);
        }
    }

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

pop的操作和push类似,原理类似,只不过反过来执行的时候,对每一个对象都执行了objc_release(obj);函数释放对象,同时page自身也会被处理掉。

不同的线程,autoreleasepool不同,autoreleasepool嵌套的时候,不会创建多个page,但会有多个边界。

上一篇 下一篇

猜你喜欢

热点阅读