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

内存管理传送门🦋🦋🦋

内存管理剖析(一)—MRC时代的手动内存管理c++

内存管理剖析(二)——定时器问题markdown

内存管理剖析(三)——iOS程序的内存布局数据结构

内存管理剖析(五)—— weak指针实现原理框架

经历过MRC时代的开发者,确定都用过autorelease方法,用于把对象交给AutoreleasePool管理,在合适的时候,自动释放对象。其实所谓的自动释放对象,就是对所管理的对象调用release方法。要想知道autorelease方法的原理,首先就须要弄清楚AutoreleasePool是个什么东东。iphone

下面来看一个段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文件执行以下命令oop

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

在生成的中间代码main.cpp中,找到main函数的底层实现以下post

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;
}
复制代码

其实若是你熟悉消息机制,上述的代码能够转化成以下形式ui

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的关系就分析到这里。

内存管理传送门🦋🦋🦋

内存管理剖析(一)—MRC时代的手动内存管理

内存管理剖析(二)——定时器问题

内存管理剖析(三)——iOS程序的内存布局

内存管理剖析(五)—— weak指针实现原理

相关文章
相关标签/搜索