iOS AutoreleasePool(自动释放池子)的原理和实现

前言

hi,勇敢的小伙伴儿们大家好,疫情终于进入一个比较好的阶段,我也在这段时间内经历了很多,有了很多感悟,能在这场大自然的灾难中活下来,都是极其幸运的人啊,致敬那些挡住黑暗的人,致敬如今的美好生活。

珍惜当下。感恩所有。

好了,不啰嗦了,今天为大家分享的是AutoreleasePool这个ARC下的大功臣。

正文

一、AutoreleasePool是什么?

AutoreleasePool是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量的release的时机。

在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

具体用代码来展示:

#import 

//生成两个全局的weak变量用来观察对象,weak修饰后引用计数不变
__weak NSString *weak_String;
__weak NSString *weak_StringAutorelease;

void createString(void) {
    
    NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"];    // 创建常规对象
    NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"]; // 创建autorelease对象
    
    weak_String = string;
    weak_StringAutorelease = stringAutorelease;
    
    NSLog(@"------in the createString()------");
    NSLog(@"%@", weak_String);
    NSLog(@"%@\n\n", weak_StringAutorelease);
}

int main(int argc, char * argv[]) {
    //AutoreleasePool
    @autoreleasepool {
        createString();
        NSLog(@"------in the createString()------");
        NSLog(@"%@", weak_String);
        NSLog(@"%@\n\n", weak_StringAutorelease);
    }
    NSLog(@"------in the main()------");
    NSLog(@"%@", weak_String);
    NSLog(@"%@", weak_StringAutorelease);

    return 0;
}

运行结果如下图所示:

iOS AutoreleasePool(自动释放池子)的原理和实现_第1张图片

我们可以清楚的看到在creatString方法中,两个对象都是正常存在的,但是在AutoreleasePool中的没有Autorelease的对象已经被释放掉了,而autorelease的对象仍然存在,在AutoreleasePool外,autorelease的对象也被释放掉了。

由此我们可以清楚的看出AutoreleasePool对weak_StringAutorelease对象的延迟释放

二.实现

知道了AutoreleasePool的作用之后,也想知道他的实现吧。

那么调整一下上面的代码,只留下main函数和@autorelease{}

#import 

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello World!");
    }
    return 0;
}

然后在终端中使用clang -rewrite-objc命令将上述代码重写成C++实现。

在Finder中找到main.cpp文件

iOS AutoreleasePool(自动释放池子)的原理和实现_第2张图片

我们可以看到main函数的实现被重写成了如下图所示的代码:

iOS AutoreleasePool(自动释放池子)的原理和实现_第3张图片

其中folders后面的字符串会不相同,但是没关系并不影响我们的分析。

通过两个代码块的对比我们可以发现,苹果通过声明一个__AtAutoreleasePool类型的局部变量__autoreleasepool实现了@autoreleasepool{}。那么这又是如何实现的呢?

这就要看看__AtAutoreleasePool的定义了:

iOS AutoreleasePool(自动释放池子)的原理和实现_第4张图片

根据构造函数(构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。)和析构函数(析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。)的特点(自动局部变量的构造函数实在程序执行到声明这个对象的位置时调用的,而对应的析构函数实在程序执行到离开这个对象的作用域时调用),我们可以将上面的两段代码简化成以下形式:

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

    /* @autoreleasepool */ {
        void *atautoreleasepoolobj = objc_autoreleasePoolPush();
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }

    return 0;
}

至此,我们可以分析出,单个自动释放池的执行过程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)。

看到这两个函数的前缀objc_我们就知道他们是runtime中的函数,接下来我们就打开runtime源码,看看他们是如何实现的。

三、源码解析

在runtime objc-723版本源码RuntimeAnalyze中查找objc_autoreleasePoolPush方法,如下图所示:

iOS AutoreleasePool(自动释放池子)的原理和实现_第5张图片

在NSObjec.mm文件中我们找到objc_autoreleasePoolPush()函数的实现:

iOS AutoreleasePool(自动释放池子)的原理和实现_第6张图片

也找到了objc_autoreleasePoolPop()函数的实现:

iOS AutoreleasePool(自动释放池子)的原理和实现_第7张图片

看到这里,我们发现这两个函数的实现都调用了AutoreleasePoolPage类中的方法。

于是我们可以断定,AutoreleasePool是通过AutoreleasePoolPage类实现的。

打开AutoreleasePoolPage类,我们可以看到它有以下属性:

iOS AutoreleasePool(自动释放池子)的原理和实现_第8张图片通过这些属性,我们可以推断出,这是一个双向链表的节点,AutoreleasePool的内存结构就是一个双向链表。

iOS AutoreleasePool(自动释放池子)的原理和实现_第9张图片而源码上的注释也可以证实我们的推测:

iOS AutoreleasePool(自动释放池子)的原理和实现_第10张图片

英语不好也没关系,谷歌翻译辅助理解:

iOS AutoreleasePool(自动释放池子)的原理和实现_第11张图片翻译的不是很准确,但也基本可以看懂了。

一个线程的autoreleasepool就是一个指针栈。
栈中存放的指针指向加入需要release的对象或者POOL_SENTINEL(哨兵对象,用于分隔autoreleasepool)。
栈中指向POOL_SENTINEL的指针就是autoreleasepool的一个标记。当autoreleasepool进行出栈操作,每一个比这个哨兵对象后进栈的对象都会release。
这个栈是由一个以page为节点双向链表组成,page根据需求进行增减。
autoreleasepool对应的线程存储了指向最新page(也就是最新添加autorelease对象的page)的指针。

通过阅读源码,我们可以分析出上述属性的作用:

magic:用来校验AutoreleasePoolPage的结构是否完整

next:指向栈顶,也就是最新入栈的autorelease对象的下一个位置

thread:指向当前线程

parent:指向父节点

child:指向子节点

depth:表示链表的深度,也就是链表节点的个数

hiwat:表示high water mark(最高水位标记)

接下来我们看看实现AutoreleasePool的几个关键函数是如何实现的。

AutoreleasePoolPage::push();

iOS AutoreleasePool(自动释放池子)的原理和实现_第12张图片

iOS AutoreleasePool(自动释放池子)的原理和实现_第13张图片

autoreleaseFullPage(obj, page)autoreleaseNoPage(obj)的区别在于autoreleaseFullPage(obj, page)会将当前page的child指向新建的page,而autoreleaseNoPage(obj)会在新建的page中先入栈一个POOL_SENTINEL(哨兵对象),再将obj入栈。

iOS AutoreleasePool(自动释放池子)的原理和实现_第14张图片

AutoreleasePoolPage::pop(ctxt);

1134行左右

    static inline void pop(void *token) //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); //通过栈顶的地址找到对应的page
        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); //从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象。

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

AutoreleasePoolPage::autorelease((id)this);


iOS AutoreleasePool(自动释放池子)的原理和实现_第15张图片

autorelease函数和push函数一样,关键代码都是调用autoreleaseFast函数向自动释放池的链表栈中添加一个对象,不过push函数的入栈的是一个哨兵对象,而autorelease函数入栈的是需要加入autoreleasepool的对象。

四、补充

上面讲了AutoreleasePoolPage的定义属性中有一个hiwat表示high water mark(最高水位标记)。那么什么是最高水位标记呢?本身AutoreleasePool就是个水池,水池总有其最高能盛水的量。既然AutoreleasePool的内存结构是一个双向链表栈,会频繁的有入栈和出栈的操作,栈中存放的对象也会有增有减,hiwat就是为了记录入栈对象最多时候对象的个数。

iOS AutoreleasePool(自动释放池子)的原理和实现_第16张图片
五、总结

看到最后,那些加入AutoreleasePool中的对象到底延迟到什么时候释放呢?

我觉得应该是在当前runloop迭代结束时释放的,因为系统在每个runloop迭代中都加入了自动释放池push和pop。

 

如果有错误烦请指出,共同学习,共同进步!


参考链接:https://www.jianshu.com/p/1b66c4d47cd7   和  http://www.cnblogs.com/L-vincen/p/6596095.html

你可能感兴趣的:(iOS)