iOS底层原理(36)-内存管理下

104 阅读4分钟

自动释放池

自动释放池的数据结构就是一个双向链表。

首先可以看到main函数:

int main(int argc, const char * argv[]) {
 
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

通过 clang 查看编译后的部分代码,全部代码请查看 main.cpp


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

......

int main(int argc, const char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2f_kt02d2w10p55r2z7mn5gc7qw0000gn_T_main_904163_mi_0);

    }
    return 0;
}

通过main.cpp文件可以看到,底层调用了两个方法objc_autoreleasePoolPush()objc_autoreleasePoolPop()

具体这两个方法底层做了哪些事情呢?这时候找到该方法所在的源文件 libobjc

objc_autoreleasePoolPush

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

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
     
   ....
   AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
		AutoreleasePoolPageData(begin(),
				objc_thread_self(),
				newParent,
			newParent ? 1+newParent->depth : 0,
				newParent ? newParent->hiwat : 0)
    { 
         if (parent) {
            parent->check();
            ASSERT(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
         }
            protect();
    }
}

AutoreleasePoolPageData

struct AutoreleasePoolPageData
{
	magic_t const magic;//⽤来校验 AutoreleasePoolPage 的结构是否完整;
	__unsafe_unretained id *next;//指向最新添加的 autoreleased 对象的下⼀个位置,初始化时指向
begin()
	pthread_t const thread;//指向当前线程
	AutoreleasePoolPage * const parent;//指向⽗结点,第⼀个结点的 parent 值为 nil
	AutoreleasePoolPage *child;//指向⼦结点,最后⼀个结点的 child 值为 nil
	uint32_t const depth;//代表深度,从 0 开始,往后递增 1
	uint32_t hiwat;//代表 high water mark 最⼤⼊栈数量标记

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

接下来,通过代码测试一下:

image.png

image.png

从打印内容可以看到,page分为hot和cold,objc也被加入到了autoreleasepool中。

autorelease是如何加入的?

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

autoreleaseFast

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        //判断page是否存在,是否已经满了,以及后续操作
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

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

autoreleaseNoPage

创建新的Page

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

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

这里看一下AutoreleasePoolPageData的参数:

AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
		AutoreleasePoolPageData(begin(),
				objc_thread_self(),
				newParent,
			newParent ? 1+newParent->depth : 0,
				newParent ? newParent->hiwat : 0)
    { 
         if (parent) {
            parent->check();
            ASSERT(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
         }
            protect();
    }
beginz()

image.png

image.png 看到这里的this是AutoreleasePoolPage

image.png 这里的sizeof(*this)指的是this所指向的内存空间的大小。

这里的56是怎么得出的呢?结构体占用内存的大小与它的成员变量有关。

AutoreleasePoolPage继承自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


struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1;//静态变量存储在全局区,不占用结构体的内存
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;
    uint32_t m[4]; //4*4=16
}

因此,16+8+8+8+8+4+4=56 , 56是这么来的。

这里return返回的地址就是 autoreleasepool 成员变量之后的位置。

objc_thread_self()

获取当前线程

static inline pthread_t objc_thread_self()
{
    return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}

自动释放池创建和压栈过程示意图

image.png

自动释放池是分页的,每一页的大小是2^12,超过这个大小,即为满页,满页之后会创建新的一页。在整个自动释放池中只有一个哨兵对象。

POP过程

objc_autoreleasePoolPop

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

pop

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

面试题

image.png

这里以 alloc 、new、copy、mutablecopy为前缀创建的对象,都不会被加进autoreleasePool中。