自动释放池(AutoreleasePool)

概念

AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

请添加图片描述

AutoreleasePool的创建和释放

创建

  • App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是
    _wrapRunLoopWithAutoreleasePoolHandler()。
  • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用
    _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

释放

  • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠)
    时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush()
    释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个
    Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
  • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

也就是说AutoreleasePool创建是在一个RunLoop事件开始之前(push),
AutoreleasePool释放是在一个RunLoop事件即将结束之前(pop)。
AutoreleasePool里的Autorelease对象的加入是在RunLoop事件中,
AutoreleasePool里的Autorelease对象的释放是在AutoreleasePool释放时。

main.m 文件里面的@autoreleasepool

使用clang将OC代码转为C++

cd 到main.m文件夹下

1.cd /Users/zhaomiaomiao/Desktop/循环引用/循环引用

clang命令

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

如图文件夹多了.cpp 文件
请添加图片描述

对比 main.cpp 与main.m 的main方法

请添加图片描述

可以发现多了__AtAutoreleasePool __autoreleasepool 这一行代码

AtAutoreleasePool的本质

 struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }

    ~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }

    void * atautoreleasepoolobj;
 };

可以发现它是一个结构体

@autoreleasepool { }

{ } 是作用域,
{ } 里会调用构造函数间接的会调用objc_autoreleasePoolPush函数:

__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}

{ } 外调用 析构函数调用objc_autoreleasePoolPop函数

  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}

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

Autoreleasepool源码

libobjc.A.dylib`objc_autoreleasePoolPush:

苹果objc源码下载地址

自动释放池是由多个autorelease page组成的双向链表,其中主要通过push及pop操作来管理:如图所示:

请添加图片描述

官方解释

请添加图片描述

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

AutoreleasePoolPage

看代码

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

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

上面的方法看上去是对 AutoreleasePoolPage 对应静态方法 push 和 pop 的封装。
下面分析一下AutoreleasePoolPage的实现,揭开AutoreleasePool的实现原理。


// 自动释放池 是一个类
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
   // 用来校验AutoreleasePoolPage 的结构是否完整
	magic_t const magic;
	// 指向最新添加的autoreleased,对象的下一个位置,初始化时指向begin();
	__unsafe_unretained id *next;
	// 指向当前线程
	pthread_t const thread;
	// 指向父节点,第一个节点parent值为nil
	AutoreleasePoolPage * const parent;
	// 指向子节点,最后一个节点的child为nil
	AutoreleasePoolPage *child;
	// 代表深度。从0开始,往后递增1;
	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 * const parent;
AutoreleasePoolPage *child;

请添加图片描述

parent和child就是用来构造双向链表的指针。parent指向前一个page, child指向下一个page。
一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入。

push

push整体流程

请添加图片描述

每当自动释放池调用objc_autoreleasePoolPush时都会把边界对象放进栈顶,然后返回边界对象,用于释放。

atautoreleasepoolobj = objc_autoreleasePoolPush();

atautoreleasepoolobj就是返回的边界对象(POOL_BOUNDARY)

push源码

objc_autoreleasePoolPush代码

objc_autoreleasePoolPush(void)
{
    //  返回的边界对象 
    return AutoreleasePoolPage::push();
}

:: : 一个函数名调用类方法

它调用AutoreleasePoolPage的类方法push:

AutoreleasePoolPage 的讲解在上方

AutoreleasePoolPage: push方法

请添加图片描述

关键的方法autoreleaseFast,并传入边界对象(POOL_BOUNDARY)

AutoreleasePoolPage:autoreleaseFast方法

请添加图片描述

上述方法分三种情况选择不同的代码执行:

  • 有 hotPage 并且当前 page 不满,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 有 hotPage 并且当前 page 已满,调用 autoreleaseFullPage 初始化一个新的页,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 无 hotPage,调用 autoreleaseNoPage 创建一个 hotPage,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中

最后的都会调用 page->add(obj) 将对象添加到自动释放池中。
hotPage 可以理解为当前正在使用的 AutoreleasePoolPage。

AutoreleasePoolPage:add方法

请添加图片描述

自动释放池会先调用 objc_autoreleasePoolPush 函数,这函数首先在当前 next 指向的位置存放一个 POOL_BOUNDARY,然后当向一个对象发送 autorelease 消息时,会在哨兵对象后面插入指向该对象的指针,之后把 next 指向刚插入的位置的下一个内存地址,

当这一页 page 快满时(即 next 即将指向栈顶——end() 位置),说明这一页 page 快满了。这时如果再加入一个对象,会先建立下一页 page,双向链表建立完成后,新的 page 的 next 指向该页的栈底——begin() 位置,之后继续向栈顶添加新的指针。

pop

pop 整体流程

请添加图片描述

自动释放池释放是传入 push 返回的边界对象,

objc_autoreleasePoolPop(atautoreleasepoolobj);

然后将边界对象指向的这一页 AutoreleasePoolPage 内的对象释放
atautoreleasepoolobj就是返回的边界对象(POOL_BOUNDARY)

pop源码

调用完前面说的objc_autoreleasePoolPush后,会返回一个POOL_BOUNDARY的地址,当对象要释放时,会调用objc_autoreleasePoolPop函数,将该POOL_BOUNDARY作为其入参,然后会执行如下操作:

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


  • page->releaseUntil(stop),对栈顶(page->next)到stop地址(POOL_SENTINEL)之间的所有对象调用objc_release(),进行引用计数减1
  • 清空page对象page->kill(),有两句注释

除非是pop(0)方式调用,这样会清理掉所有page对象;
否则,在当前page存放的对象大于一半时,会保留一个空的子page,
这样估计是为了可能马上需要新建page节省创建page的开销。

  1. 根据传入的POOL_BOUNDARY(push后得到的那个)找到其所在的page;
  2. 从 hotPage 的 next 指针开始往前查找,向找到的每个指针调用 memset 方法以擦除指针所占内存,
  3. 再调用 objc_release 方法释放该指针指向的对象,
  4. 直到前一步所找到的 page 的 POOL_BOUNDARY 为止(可往前跨越多个 page),并且在释放前,next 指针也会往回指向正确的位置。
  5. 当有嵌套的 autoreleasepool 时,会清除一层后再清除另一层,因为 pop 是会释放到上次 push
    的位置为止,就像剥洋葱一样,每次一层,互不影响。

autorelease

autorelease 调用栈

- [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                ├── id *add(id obj)
                ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                │   └── id *add(id obj)
                └── static id *autoreleaseNoPage(id obj)
                    ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    └── id *add(id obj)

在autorelease方法的调用栈中,最终都会调用上面提到的 autoreleaseFast方法,将当前对象加到AutoreleasePoolPage 中。

这一小节中这些方法的实现都非常容易,只是进行了一些参数上的检查,最终还要调用autoreleaseFast方法:

autorelease 源码

inline id objc_object::rootAutorelease() {
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj) {
     // 添加obj对象到autoreleasepool的链表栈中
   id *dest __unused = autoreleaseFast(obj);
   return obj;
}

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

hiwat

AutoreleasePool的内存结构是一个双向链表栈,会频繁的有入栈和出栈的操作,栈中存放的对象也会有增有减,hiwat就是为了记录入栈对象最多时候对象的个数。

请添加图片描述

autorelease 对象会在什么时候释放?

使用@autoreleasepool,会在大括号结束时释放

不使用 @autoreleasepool,这个会由系统自动释放,释放时机是在当前 runloop 结束时释放,因为系统会自动为每个
runloop 执行自动释放池的 push 和 pop 操作

关于内存管理的方法,目前来说,有三种:

C/C++的完全由程序员管理(paring new/malloc & delete/free);

Garbage Collection;

Reference Counting;

第一种比较原始;Cocoa Touch的Reference Counting对比Garbage Collection,有一个致命的弱点:无法释放循环引用的对象,所以要注意不要造成循环引用。

Runloop和Autorelease

iOS在主线程的Runloop中注册了2个Observer

  • 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
  • 第2个Observer
    <1> 监听了kCFRunLoopBeforeWaiting事件,会调用 objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
    <2> 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

什么对象会加入Autoreleasepool中

  • 使用alloc、new、copy、mutableCopy的方法进行初始化时,由系统管理对象,在适当的位置release。
  • 使用array会自动将返回值的对象注册到Autoreleasepool。
  • __weak修饰的对象,为了保证在引用时不被废弃,会注册到Autoreleasepool中。
  • id的指针或对象的指针,在没有显示指定时会被注册到Autoleasepool中。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值