OC底层原理探索之内存管理下

473 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

自动释放池autoreleasepool

找到工程的main函数,按照管理xcRun一下,生成.cpp文件

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

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2f_kt02d2w10p55r2z7mn5gc7qw0000gn_T_main_904163_mi_0);
    }
    return 0;
}

__AtAutoreleasePool __autoreleasepool = @autoreleasepool 我们发现这是一个结构体,里面含有一个构造函数和一个析构函数。也就是说在这个自动释放池自动调用了objc_autoreleasePoolPush(),作用域结束之后自动调用了objc_autoreleasePoolPop

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

打开libObjc源码搜索objc_autoreleasePoolPush

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

自动释放池的结构

自动释放池是一个双向链表。AutoreleasePoolPage继承于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
	// ...
};
  • magic⽤来校验AutoreleasePoolPage的结构是否完整;
  • next指向最新添加的autoreleased对象的下⼀个位置,初始化时指向begin();
  • thread指向当前线程;
  • parent指向⽗结点,第⼀个结点的parent值为nil;
  • child指向⼦结点,最后⼀个结点的child值为nil;
  • depth代表深度,从0开始,往后递增1;
  • hiwat代表highwatermark最⼤⼊栈数量标记

首先修改配置在MRC下

image.png _objc_autoreleasePoolPrintApple给出的一个可以打印autoreleasePool内容的函数

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        // class_data_bits_t
        NSObject *obj = [[[NSObject alloc] init] autorelease];
        _objc_autoreleasePoolPrint();
    }
    return 0;
}

image.png 这里为什么有两个对象需要释放呢? ​

自动释放池的创建和压栈

我们需要查看下AutoreleasePoolPage::push调用发生了什么 autoreleaseFast -> autoreleaseNoPage(第一次创建走这里,创建page) ->AutoreleasePoolPage() -> page->add(POOL_BOUNDARY) -> page->add(obj) ,在构造函数AutoreleasePoolPage的时候,可以打印出自动释放池成员变量的大小 image.png 所以对象压栈是在56位之后才开始 第一个对象:**[0x10200e038] ################ POOL 0x10200e038** 是边界也就是之前说的哨兵 第二个对象:**[0x10200e040] 0x10104fb00 NSObject** 是NSObject 自动释放池.png

既然自动释放池是分页的结构,那么一页的大小是多少

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
	friend struct thread_data_t;

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

PAGE_MIN_SIZE = 1 << PAGE_MIN_SHIFT = 1 << 12 = 2^12 = 4096 = 4kb 既然我们知道了一页的大小,那么我们for循环下,看下创建新的page的时候有什么不同? image.png image.png 通过打印的日志我们可以知道:在新的页面中并没有加入所谓的哨兵对象。 ​

调用objc_autoreleasePoolPop出栈,AutoreleasePoolPage::pop(ctxt) -> popPage

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

首先找到parent然后kill掉自身,并把parent置为setHotPage 来到kill()函数里面是do while循环来置空,总之加入在自动释放池被管理的对象,当接收到pop指令的时候,会把压栈进来的对象都pop出去,当发现到只有哨兵对象的时候,就表示page为空。

临时变量什么时候释放

在出了自动释放池的作用域之外就会释放

自动释放池能否嵌套使用

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
        NSObject *obj = [[[NSObject alloc] init] autorelease];
        @autoreleasepool {
            NSLog(@"嵌套的自动释放池");
            NSObject *obj1 = [[[NSObject alloc] init] autorelease];
            _objc_autoreleasePoolPrint();
        }
        _objc_autoreleasePoolPrint();
    }
    return 0;
}

image.png 一个自动释放池有一个哨兵对象,自动释放池可以嵌套使用。 ​

自动释放池的拓展

以上都是在MRC情况下的调试,在MRC下如果不加autorelease的对象是不会自动添加到自动释放池的 image.png 在实际的开发中,我们都是在ARC环境下来开发的,所以我们把配置切回到ARC环境下,结果发现在ARC创建的NSObject并不会加入到自动释放池。为什么呢? image.png原因:alloc、new、 copy、 mutableCopy在LLVM编译期间不会加入到自动释放池,这个可以自己测试下。