内存管理剖析(六)——autorelease原理分析

经历过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 {}经过编译之后发生了如下转变

这里多了个__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函数,其实它本质上就是下面这个形式

上面是单层@autoreleasepool {}的情况,那么如果有多层@autoreleasepool {}嵌套在一起,就可以按照同样的规则来拆解

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++类 AutoreleasePoolPagepush()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个字节的内存地址。

AutoreleasePoolPage的begin()和end()

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

每个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;
    AutoreleasePoolPage结构示意图

【第一次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;//将当前页对象返回
    }

因为我们一开始将keyEMPTY_POOL_PLACEHOLDER绑定过,因此这里返回空,表明当前页空,还未被创建,因此我们返回到autoreleaseFast方法里面,将会调用autoreleaseNoPage(obj)函数,根据我们上面对这个函数步骤的注释,这一次程序应该会走到函数的最后一部分

主要做了下面几件事:

  • 初始化第一个AutoreleasePoolPage
  • 将其设置成当前页(hot)
  • 最初的EMPTY_POOL_PLACEHOLDER会使pushExtraBoundary置为true,因此这里需要为第一个AutoreleasePoolPage先入栈一个POOL_BOUNDARY
  • 最后用add(obj)将传入的自动释放对象obj入栈

上面add()函数的具体功能,其实就是将obj的值赋值给当前AutoreleasePoolPagenext指针指向的内存空间,然后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);
    }

它的作用就是把当前新创建的AutoreleasePoolPagekey绑定起来,日后hotPage()函数就可以通过key直接拿到当前页。

【再一次调用autorelease】

如果我们继续对新的对象执行autorelease操作,同样会来到函数,但由于AutoreleasePoolPage对象已经存在了,如果当前page未满,会走如下函数

image.png

也就是直接通过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指针去寻找下一个未满的pageAutoreleasePoolPage对象之间是通过childparent指针形成的双向链表结构,就是为了在这个时候使用的。同样,在清空释放池对象的时候,如果当前释放池完全空了,则会通过parent指针去寻找上层的释放池。

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

除了系统在main函数里加上的最初的一层@autoreleasepool {}之外,有时候我们自己的代码里面可能会也会使用@autoreleasepool {},方便对一些对象进行更为灵活的内存管理。那么我们手动加的@autoreleasepool {}肯定是嵌套在main函数@autoreleasepool {}内部的,相当于

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

}

现在我们再次来看一下这一次AutoreleasePoolPage::push();会如何执行。同样程序会执行到autoreleaseFast(POOL_BOUNDARY);

POOL_BOUNDARY会被传入autoreleaseFast函数,并且也会通过add()或者autoreleaseFullPage()被添加到AutoreleasePoolPage对象的页空间上。其实就是和普通的[obj autorelease]的流程一样,只不过这次是obj = POOL_BOUNDARY,显然这是为了一个新的@autoreleasepool{}做准备。

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

分析完了源码,现在通过图例来展示一下@autoreleasepool的实现原理。
【假设】为方便展示每页AutoreleasePoolPage只能存放3个释放对象,如下

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

这个问题就要搞清楚@autoreleasepool{}的另一半AutoreleasePoolPage::pop(atautoreleasepoolobj);做了什么。一起来看一看

其中的核心函数便是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方法。可以通过下图的示例来体会一下。

AutoreleasePoolPage::pop()的核心步骤


AutoreleasePool与RunLoop

通过上面的研究,我们知道@autoreleasepool{}的作用,实际上就是在作用域的头和尾分别调用了objc_autoreleasePoolPush();objc_autoreleasePoolPop()函数,但是在iOS项目当中,@autoreleasepool{}的作用域是什么时候开始,什么时候结束呢?这就需要了解我们之前研究过的另一个知识点RunLoop。我们知道,除非我们手动启动子线程的RunLoop,否则程序里面只有主线程有RunLoop,这是系统默认开启的。下面我们来看一下主线程的RunLoop肚子里都有什么宝贝。

我们可以随便新建一个iOS项目,在ViewControllerviewDidLoad方法里可以直接打印当前RunLoop对象(即主线程的RunLoop对象)

@implementation ViewController

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

@end

打印结果是洋洋洒洒的一大堆,如果你还不熟悉RunLoop的结构,可以参考我的Runloop的内部结构与运行原理,里面应该说的比较清楚了。我们可以在打印结果的common mode items部分,找到两个跟autorelease相关的observer,如下图所示

runloop中的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 =(kCFRunLoopExitkCFRunLoopBeforeWaiting
  • activities = 0x1 =(kCFRunLoopEntry
    因此这三个状态被监听到的时候,就会调用_wrapRunLoopWithAutoreleasePoolHandler函数。这个函数实际上是按照下图的示意运作
  • 监听到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的关系就分析到这里。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容