天天看点

内存管理——autorelease原理分析

内存管理系列文章

内存管理—MRC时代的手动内存管理

内存管理—weak的实现原理

内存管理——autorelease原理分析

内存管理——定时器问题

iOS程序的内存布局

经历过MRC时代的开发者,肯定都用过

autorelease

方法,用于把对象交给

AutoreleasePool

管理,在合适的时候,自动释放对象。其实所谓的自动释放对象,就是对所管理的对象调用

release

方法。要想知道

autorelease

方法的原理,首先就需要弄清楚

AutoreleasePool

是个什么东东。

下面来看一个段MRC环境下的代码,为什么要在MRC下讨论这个问题呢?因为ARC会为我们在合适的地方自动加上

autorelease

代码,并且不允许我们手动调用该方法了,为了方便研究

autorelease

原理,我们还是得回到MRC。

****************** main.m *****************
#import <Foundation/Foundation.h>
#import "CLPerson.h"

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

    NSLog(@"pool--start");
    @autoreleasepool { 
        CLPerson *p = [[[CLPerson alloc] init] autorelease];
    } 
    NSLog(@"pool--end");

    return 0;
}

************** CLPerson.m **************
#import "CLPerson.h"

@implementation CLPerson

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [super dealloc];
}
@end

****************** 打印结果 *******************
2019-08-27 16:37:15.141523+0800 Interview16-autorelease[11602:772121] pool--start
2019-08-27 16:37:15.141763+0800 Interview16-autorelease[11602:772121] -[CLPerson dealloc]
2019-08-27 16:37:15.141775+0800 Interview16-autorelease[11602:772121] pool--end
           

概括一下看到的表面现象:

CLPerson

实例对象

p

是在

@autoreleasepool {}

大括号结束的时候被释放的。

那么

@autoreleasepool {}

到底做了什么呢?我们在命令行窗口里对

main.m

文件执行如下命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

在生成的中间代码

main.cpp

中,找到

main

函数的底层实现如下

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
    }
    return 0;
}
           

其实如果你熟悉消息机制,上述的代码可以转化成如下形式

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { 
        __AtAutoreleasePool __autoreleasepool; 
        CLPerson *p = [[[CLPerson alloc] init] autorelease];
    }
    return 0;
}
           

我们观察可发现

@autoreleasepool {}

经过编译之后发生了如下转变

内存管理——autorelease原理分析

这里多了个

__AtAutoreleasePool

,它其实是个c++的结构体,可以在

main.cpp

里搜索到它的定义如下

struct __AtAutoreleasePool {
    //构造函数-->可以类比成OC的init方法,在创建时调用
  __AtAutoreleasePool()
    {
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    
    //析构函数-->可以类比成OC的dealloc方法,在销毁时调用
  ~__AtAutoreleasePool()
    {
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    
  void * atautoreleasepoolobj;
};
           

如果你还不了解C++语法也无妨,它跟OC的类相似,可以有函数(方法),上面的这个结构体

__AtAutoreleasePool

里面有已经有两个函数,

  • 一个构造函数

    __AtAutoreleasePool()

    -->

    atautoreleasepoolobj = objc_autoreleasePoolPush();

    ,结构体被创建时调用,用于结构体的初始化
  • 一个析构函数

    ~__AtAutoreleasePool()

    -->

    objc_autoreleasePoolPop(atautoreleasepoolobj);

    ,结构体被销毁时调用

再回到我们的

main

函数,其实它本质上就是下面这个形式

内存管理——autorelease原理分析

上面是单层

@autoreleasepool {}

的情况,那么如果有多层

@autoreleasepool {}

嵌套在一起,就可以按照同样的规则来拆解

内存管理——autorelease原理分析
objc_autoreleasePoolPush() & objc_autoreleasePoolPop()

接下来我们就来探究一下这两个函数的实现逻辑。在objc4源码的

NSObject.mm

文件里可以找到它们的实现

*************** NSObject.mm (objc4) ******************
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

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

可以看到,它们分别调用了C++类

AutoreleasePoolPage

push()

pop()

函数。要想继续深入后续函数的实现逻辑,我们需要先来看一看这个

AutoreleasePoolPage

的内部结构,它的内容不少,有大量函数,但是我们首先需要理清楚它的成员变量,这些是可变化的,可操控的,所以去掉函数和一些静态常量,可以将

AutoreleasePoolPage

结构简化如下

class AutoreleasePoolPage 
{
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}
           

根据其命名,中文释义成自动释放池页,有个页的概念。我们知道自动释放池,是用来存放对象的,这个“页”就说明释放池的结构体应该有页面篇幅限制(内存空间大小)。具体多大呢?来看一下

AutoreleasePoolPage

的两个函数

id * begin() {

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

id * end() {
        return (id *) ((uint8_t *)this+SIZE);
}
           

begin()

函数返回一个指针,指向自身最后一个成员变量之后的内存地址(相当于越过了自身所占用的内存空间)

end()

里面有一个

SIZE

,我们看看它的定义

static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif

********************************************
#define PAGE_MAX_SIZE           PAGE_SIZE
********************************************
#define PAGE_SIZE               I386_PGBYTES
********************************************
#define I386_PGBYTES            4096            /* bytes per 80386 page */

           

可以看到,

SIZE

实际上是

4096

。这就是说

end()

函数,得到的是一个指针,指向

AutoreleasePoolPage

对象地址之后的第

4096

个字节的内存地址。

内存管理——autorelease原理分析

通过以上掌握的信息,我们先抛出结论,然后再继续通过源码加深理解。

每个

AutoreleasePoolPage

对象占

4096

个字节,其中成员变量共占用

8字节

*

7

=

56个字节

。剩余的

4040

个字节的空间就是用来存储自动释放对象的。

因为一个

AutoreleasePoolPage

对象的内存是有限的,程序里面可能有很多对象会被加入自动释放池,因此可能会出现多个

AutoreleasePoolPage

对象来共同存放自动释放对象。所有的

AutoreleasePoolPage

对象是以双向链表的形式(数据结构)连接在一起的。

AutoreleasePoolPage

对象的各成员变量含义如下
  • magic_t const magic;

  • id *next;

    指向

    AutoreleasePoolPage

    内下一个可以用来存放自动释放对象的内存地址
  • pthread_t const thread;

    自动释放池所属的线程,说明它不能跟多个线程关联。
  • AutoreleasePoolPage * const parent;

    指向上一页释放池的指针
  • AutoreleasePoolPage *child;

    指向下一页释放池的指针
  • uint32_t const depth;

  • uint32_t hiwat;

    内存管理——autorelease原理分析

【第一次AutoreleasePoolPage::push();】

接下来,我们就正式开始研究

AutoreleasePoolPage::push();

。假设我们现在是处在项目的main函数的第一个

@autoreleasepool {}

开始的地方,也就是整个程序将会第一次去调用

push()

函数:

#   define POOL_BOUNDARY nil

static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {//Debug模式下,每个autorelease pool都会创建新页
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {//标准情况下,调用autoreleaseFast()函数
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
           

其中

POOL_BOUNDARY

就是

nil

的宏定义,忽略Debug模式,我们只看正常模式,那么

push()

将会调用

autoreleaseFast(POOL_BOUNDARY)

得到一个

id *dest

并将其返回给上层函数。查看一下这个

autoreleaseFast()

,看看它到底能给我们返回什么

static inline id *autoreleaseFast(id obj)
    {
        //拿到当前可用的AutoreleasePoolPage对象page
        AutoreleasePoolPage *page = hotPage();
        //(1)如果page存在&&page未满,则直接增加obj
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {//(2)如果满了,则调用autoreleaseFullPage(obj, page);
            return autoreleaseFullPage(obj, page);
        } else {//(3)如果没有页面,则调用autoreleaseNoPage(obj);
            return autoreleaseNoPage(obj);
        }
    }
           

因为是整个程序第一次push操作,因此page对象还不存在,所以会按照情况(3)走,也就是

autoreleaseNoPage(obj);

,实现如下

static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        
        /*--"No page"
         1.可以表示当前还没有任何pool被创建(pushed)
         2.也可以表示已经创建了一个empty placeholder pool(空释放池占位符),只是还没添加任何内容
         */
        assert(!hotPage());
        
        
        
        
        
        
        //标签-->是否需要增加额外的POOL_BOUNDARY
        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            /*
             如果存在EmptyPoolPlaceholder(空占位符pool),就修改标签为true,
             后面就需要依据此标签增加额外的POOL_BOUNDARY
             */
            pushExtraBoundary = true;
        }
        
        /*
         如果传入的obj不等于POOL_BOUNDARY(nil)并且找不到当前pool(丢失了),返回nil
         */
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         pthread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        
        /*
         ♥️♥️♥️♥️如果传入的是POOL_BOUNDARY,并且不在Debug模式,
         会调用setEmptyPoolPlaceholder()设置一个EmptyPoolPlaceholder
         */
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            return setEmptyPoolPlaceholder();
        }
        
        
        

        // 初始化第一个AutoreleasePoolPage
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        //将其设置成当前页(hot)
        setHotPage(page);
        
        // 根据pushExtraBoundary标签决定是否多入栈一个POOL_BOUNDARY
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // 将传入的obj入栈,通过 add()函数
        return page->add(obj);
    }
           

因为此时还没有创建过

AutoreleasePoolPage

,并且也没有设置过

EmptyPoolPlaceholder

,因此程序会命中代码中♥️♥️♥️♥️标记出的代码,调用

setEmptyPoolPlaceholder();

,该函数实现如下

#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
********************************************

static inline id* setEmptyPoolPlaceholder()
    {
        assert(tls_get_direct(key) == nil);
        tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
        return EMPTY_POOL_PLACEHOLDER;
    }
           

可以看到实际上就是将

key

(id*)1

绑定起来,这个

key

是一个静态常量,最后将这个

(id*)1

作为一个空释放池池占位符返回,这样整个程序的第一个

push()

函数结束,结果是生成了一个

EMPTY_POOL_PLACEHOLDER (也就是(id*)1)

作为释放池占位符。

【第一次调用autorelease】

接着上面的过程,我们在

push()

后,第一次对某个对象执行

autorelease

方法时,看一下

autorelease

的内部做了什么,先找到其源码如下

- (id)autorelease {
    return ((id)self)->rootAutorelease();//?️从这里往下走
}

************************************************
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()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);//?️从这里往下走
}

************************************************
static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);//⚠️最终走到了这个方法
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }


           

通过逐层递进,我们看到

autorelease

方法最终又来到了

autoreleaseFast()

函数

static inline id *autoreleaseFast(id obj)
    {
        //拿到当前可用的AutoreleasePoolPage对象page
        AutoreleasePoolPage *page = hotPage();
        //(1)如果page存在&&page未满,则直接增加obj
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {//(2)如果满了,则调用autoreleaseFullPage(obj, page);
            return autoreleaseFullPage(obj, page);
        } else {//(3)如果没有页面,则调用autoreleaseNoPage(obj);
            return autoreleaseNoPage(obj);
        }
    }
           

那么这一次,我们看看第一句代码里面

hotPage();

得到的是什么

static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        //如果检查到key有绑定EMPTY_POOL_PLACEHOLDER,返回nil
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        
        if (result) result->fastcheck();
        return result;//将当前页对象返回
    }
           

因为我们一开始将

key

EMPTY_POOL_PLACEHOLDER

绑定过,因此这里返回空,表明当前页空,还未被创建,因此我们返回到

autoreleaseFast

方法里面,将会调用

autoreleaseNoPage(obj)

函数,根据我们上面对这个函数步骤的注释,这一次程序应该会走到函数的最后一部分

内存管理——autorelease原理分析

主要做了下面几件事:

  • 初始化第一个

    AutoreleasePoolPage

  • 将其设置成当前页(hot)
  • 最初的

    EMPTY_POOL_PLACEHOLDER

    会使

    pushExtraBoundary

    置为

    true

    ,因此这里需要为第一个

    AutoreleasePoolPage

    先入栈一个

    POOL_BOUNDARY

  • 最后用

    add(obj)

    将传入的自动释放对象

    obj

    入栈

上面

add()

函数的具体功能,其实就是将

obj

的值赋值给当前

AutoreleasePoolPage

next

指针指向的内存空间,然后

next

再进行

++

操作,移向下一段可用内存空间,方便下一次存放自动释放对象的时候使用。如下

id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;//先赋值,再++
        protect();
        return ret;
    }
           

另外需要注意一下这里的

setHotPage(page)

函数,实现如下

static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        if (page) page->fastcheck();
        tls_set_direct(key, (void *)page);
    }
           

它的作用就是把当前新创建的

AutoreleasePoolPage

key

绑定起来,日后

hotPage()

函数就可以通过

key

直接拿到当前页。

####【再一次调用autorelease】

如果我们继续对新的对象执行

autorelease

操作,同样会来到函数,但由于

AutoreleasePoolPage

对象已经存在了,如果当前

page

未满,会走如下函数

内存管理——autorelease原理分析

也就是直接通过

add(obj)

函数将

obj

对象入栈

我们之前说过,一个

AutoreleasePoolPage

对象能存放的自动释放对象数量是有限的,一个自动释放对象就是一个指针,占8字节,而

AutoreleasePoolPage

对象可用的空间是4040个字节,也就是可以存放505个对象(指针),所以一页

AutoreleasePoolPage

是有可能满页的,这个时候,

autoreleaseFast

就会调用

autoreleaseFullPage(obj, page);

函数,它的实现如下

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 {//通过child指针拿到下一个没有满的page对象
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);//先将上面获取的page设置为当前页(hot)
        return page->add(obj);//通过add函数将obj存入该page
    }
           

其实上面就是通过

AutoreleasePoolPage

对象的

child

指针去寻找下一个未满的

page

AutoreleasePoolPage

对象之间是通过

child

parent

指针形成的双向链表结构,就是为了在这个时候使用的。同样,在清空释放池对象的时候,如果当前释放池完全空了,则会通过

parent

指针去寻找上层的释放池。

【再一次AutoreleasePoolPage::push();】

除了系统在

main

函数里加上的最初的一层

@autoreleasepool {}

之外,有时候我们自己的代码里面可能会也会使用

@autoreleasepool {}

,方便对一些对象进行更为灵活的内存管理。那么我们手动加的

@autoreleasepool {}

肯定是嵌套在main函数

@autoreleasepool {}

内部的,相当于

int main(int argc, const char * argv[]) {
        @autoreleasepool {//这是系统加的第一层
                @autoreleasepool {}//这是我们可能会添加的内层嵌套
        }

}
           

现在我们再次来看一下这一次

AutoreleasePoolPage::push();

会如何执行。同样程序会执行到

autoreleaseFast(POOL_BOUNDARY);

内存管理——autorelease原理分析
内存管理——autorelease原理分析

POOL_BOUNDARY

会被传入

autoreleaseFast

函数,并且也会通过

add()

或者

autoreleaseFullPage()

被添加到

AutoreleasePoolPage

对象的页空间上。其实就是和普通的

[obj autorelease]

的流程一样,只不过这次是

obj

=

POOL_BOUNDARY

,显然这是为了一个新的

@autoreleasepool{}

做准备。

POOL_BOUNDARY

到底是拿来干嘛的呢?一会你就知道了。

分析完了源码,现在通过图例来展示一下

@autoreleasepool

的实现原理。

【假设】为方便展示每页

AutoreleasePoolPage

只能存放3个释放对象,如下
内存管理——autorelease原理分析

autorelease对象什么时候回调用release方法呢?

这个问题就要搞清楚

@autoreleasepool{}

的另一半

AutoreleasePoolPage::pop(atautoreleasepoolobj);

做了什么。一起来看一看

内存管理——autorelease原理分析

其中的核心函数便是

releaseUntile(stop)

,这里的

stop

实际上传入的就是

POOL_BOUNDARY

,进入该函数

void releaseUntil(id *stop) 
    {
        
        
        while (this->next != stop) {//?如果next指向POOL_BOUNDARY,跳出循环?
            
            //?拿到当前页
            AutoreleasePoolPage *page = hotPage();

            //??当前页如果为空,通过parent拿到上一个AutoreleasePoolPage对象作为当前页
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            
            //???通过 --next 拿到当前页栈顶的对象
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
                //????如果obj不是POOL_BOUNDARY,就进行[obj release]
                objc_release(obj);
            }
        }

        setHotPage(this);
    }
           

pop()

核心步骤已经在上面函数里的注释体现出来。也就是说,当最内层的

@autoreleasepool{}

作用域结束调用其对应的

pop()

函数时,会从

AutoreleasePoolPage

链表的当前页里面找到栈顶的对象,逐个开始释放,直到遇到

POOL_BOUNDARY

就停下来,这样,就代表这一层的

@autorelease{}

内所包含的所有对象都完成了

release

方法调用。

当程序走到上一层的

@autoreleasepool{}

作用域结束的地方,又回执行上面的流程,对其包含的对象一次调用

release

方法。可以通过下图的示例来体会一下。

内存管理——autorelease原理分析

AutoreleasePool与RunLoop

通过上面的研究,我们知道

@autoreleasepool{}

的作用,实际上就是在作用域的头和尾分别调用了

objc_autoreleasePoolPush();

objc_autoreleasePoolPop()

函数,但是在iOS项目当中,

@autoreleasepool{}

的作用域是什么时候开始,什么时候结束呢?这就需要了解我们之前研究过的另一个知识点RunLoop。我们知道,除非我们手动启动子线程的RunLoop,否则程序里面只有主线程有RunLoop,这是系统默认开启的。下面我们来看一下主线程的RunLoop肚子里都有什么宝贝。

我们可以随便新建一个iOS项目,在

ViewController

viewDidLoad

方法里可以直接打印当前RunLoop对象(即主线程的RunLoop对象)

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%@",[NSRunLoop currentRunLoop]);
}

@end
           

打印结果是洋洋洒洒的一大堆,如果你还不熟悉RunLoop的结构,可以参考我的Runloop的内部结构与运行原理,里面应该说的比较清楚了。我们可以在打印结果的

common mode items

部分,找到两个跟

autorelease

相关的

observer

,如下图所示

内存管理——autorelease原理分析

具体如下

<CFRunLoopObserver 0x600003f3c640 [0x10a2fdae8]>
{
valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e17ac9d), 
context = 
<CFArray 0x6000000353b0 [0x10a2fdae8]>
    {
    type = mutable-small, count = 1, values = (0 : <0x7f91ff802058>)
    }
}


<CFRunLoopObserver 0x600003f3c500 [0x10a2fdae8]>
{
valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e17ac9d), 
context = 
<CFArray 0x6000000353b0 [0x10a2fdae8]>
    {
    type = mutable-small, count = 1, values = (0 : <0x7f91ff802058> )
    }
}
           

我们可以看到,这两个监听器分监听的状态分别是

  • activities = 0xa0

    (对应十进制的

    160

  • activities = 0x1

    (对应十进制的

    1

    这两个状态怎么解读呢?我们可以在CF框架的RunLoop源码里面找到对应的定义

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),************十进制1---(进入loop)
    kCFRunLoopBeforeTimers = (1UL << 1),****十进制2
    kCFRunLoopBeforeSources = (1UL << 2),**十进制4
    kCFRunLoopBeforeWaiting = (1UL << 5),***十进制32----(loop即将休眠)
    kCFRunLoopAfterWaiting = (1UL << 6),*****十进制64
    kCFRunLoopExit = (1UL << 7),**************十进制128----(退出loop)
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
           

根据RunLoop状态的枚举值可以看出,

160 = 128 + 32

,也就是说

  • activities = 0xa0

    =(kCFRunLoopExit | kCFRunLoopBeforeWaiting)
  • activities = 0x1

    =(kCFRunLoopEntry)

    因此这三个状态被监听到的时候,就会调用

    _wrapRunLoopWithAutoreleasePoolHandler

    函数。这个函数实际上是按照下图的示意运作
    内存管理——autorelease原理分析
  • 监听到kCFRunLoopEntry事件,调用

    objc_autoreleasePoolPush();

  • 监听到kCFRunLoopBeforeWaiting事件,调用

    objc_autoreleasePoolPop()

    ,然后调用

    objc_autoreleasePoolPush();

  • 监听到kCFRunLoopExit事件,调用

    objc_autoreleasePoolPop()

根据上面的分析,我们可以总结,除了程序启动(对应kCFRunLoopEntry)和程序退出(对应kCFRunLoopExit)会调用一次

objc_autoreleasePoolPush();

objc_autoreleasePoolPop()

外,程序的运行过程中,每当RunLoop即将休眠,被

observer

监听到kCFRunLoopBeforeWaiting状态时,会先调用一次

objc_autoreleasePoolPop()

,这样就将当前的

autoreleasepool

里面的对象逐个调用

release

方法,相当于清空释放池子;紧接着再调用一次

objc_autoreleasePoolPush();

,相当于开启一个新的释放池,等待RunLoop醒来后的下一次循环使用。

自动释放池的对象什么时候会被调用release方法呢?

RunLoop的每一圈循环过程中,调用过

autorelease

方法的对象(也就是被加入

AutoreleasePoolPage

的对象),会在当次循环即将进入休眠状态的时候,被调用

release

方法,也可以说是被释放了。

好了,AutoreleasePool的原理以及它和RunLoop的关系就分析到这里