iOS:AutoReleasePool释放对象的时刻

大家好,我是OB!今天来聊聊AutoReleasePool!

一、AutoReleasePool

1:AutoReleasePool底层结构

自动释放池,如果有大量的临时数据可以使用AutoReleasePool,能更加快捷的释放临时对象。

下面来看看他的底层结构

OC的autoreleasepool在经过clang编译后,变成了结构体__AtAutoreleasePool

struct __AtAutoreleasePool {
    void * atautoreleasepoolobj;
    __AtAutoreleasePool() {
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool() {
    	objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
};

然后查阅NSObject源码发现了c++的对象AutoreleasePoolPage

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

那么AutoreleasePoolPage又是什么呢?继续看NSObject.mm发现

class AutoreleasePoolPage {
#   define POOL_BOUNDARY nil
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    
    AutoreleasePoolPage(...) {}
    ~AutoreleasePoolPage() {}
public:
    static inline id autorelease(id obj) {}
    static inline void *push(){}
    static inline void pop(void *token){}
};

AutoReleasePool是一个由AutoreleasePoolPageNode组成的双向链表的数据结构。AutoreleasePoolPage的大小是4096,也就是4k。

AutoreleasePoolPage的大小是4096字节?
CPU是通过寻址来访问内存的。32位CPU的寻址宽度是 0~0xFFFFFFFF ,计算后得到的大小是4G,也就是说可支持的物理内存最大是4G。
但在实践过程中,碰到了这样的问题,程序需要使用4G内存,而可用物理内存小于4G,导致程序不得不降低内存占用。
为了解决此类问题,现代CPU引入了 MMU(Memory Management Unit 内存管理单元)。

MMU 的核心思想是利用虚拟地址替代物理地址,即CPU寻址时使用虚址,由 MMU 负责将虚址映射为物理地址。
MMU的引入,解决了对物理内存的限制,对程序来说,就像自己在使用4G内存一样。

内存分页(Paging)是在使用MMU的基础上,提出的一种内存管理机制。它将虚拟地址和物理地址按固定大小(4K)分割成页(page)和页帧(page frame),并保证页与页帧的大小相同。

2:AutoReleasePool方法解析
push()方法

AutoreleasePoolPage的push()方法主要是将POOL_BOUNDARY添加到page中,放在page的开始位置,之后删除就是从hotPage的尾部删除,直到遇到POOL_BOUNDARY的位置,表示删除完毕。

POOL_BOUNDARY 哨兵对象,其实就是地址为nil的对象

static inline void *push() {
    id *dest;
    if (DebugPoolAllocation) {
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}


static inline id *autoreleaseFast(id obj) {
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {
        return page->add(obj);
    } else if (page) {
        return autoreleaseFullPage(obj, page);
    } else {
        return autoreleaseNoPage(obj);
    }
}
id *add(id obj) {
    assert(!full());
    unprotect();
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;
    protect();
    return ret;
}

哨兵对象/边界对象(POOL_BOUNDARY)

#define POOL_BOUNDARY nil
边界对象其实就是nil的别名,而它的作用事实上也就是为了起到一个标识的作用。

每当自动释放池初始化调用objc_autoreleasePoolPush方法时,总会通过AutoreleasePoolPage的push方法,将POOL_BOUNDARY放到当前page的栈顶,并且返回这个边界对象;
而在自动释放池释放调用objc_autoreleasePoolPop方法时,又会将边界对象以参数传入,这样自动释放池就会向释放池中对象发送release消息,直至找到第一个边界对象为止。

pop()方法

AutoreleasePoolPage的pop()方法主要是出栈。找到hotPage,并从hotPage尾部release对象,直到遇到POOL_BOUNDARY的位置

static inline void pop(void *token) {
    AutoreleasePoolPage *page;
    page = pageForPointer(token);
    stop = (id *)token;
    page->releaseUntil(stop); 
}
void releaseUntil(id *stop) {
    while (this->next != stop) {
        // code ...
        if (obj != POOL_BOUNDARY) {
            objc_release(obj);
        }
    }
    setHotPage(this);
}

AutoReleasePool嵌套

可以通过extern void _objc_autoreleasePoolPrint(void);去观察AutoReleasePool,
多个AutoReleasePool嵌套时,每添加一个池子,就会执行push()方法,就会添加一个哨兵对象,删除时也是删除到哨兵对象为止,表示当前内部的page释放完毕。

extern void _objc_autoreleasePoolPrint(void);
#import "OBAnimal.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for (int i = 0; i < 10; i ++) {
            OBAnimal * dog = [[[OBAnimal alloc] init] autorelease];
        }
        
        @autoreleasepool {
            for (int i = 0; i < 5; i ++) {
                OBAnimal * dog = [[[OBAnimal alloc] init] autorelease];
            }
            _objc_autoreleasePoolPrint();
        }
    }
    return 0;
}

嵌套时,push操作会获取当前的hotpage。也就是说,嵌套的pool只会有一个链表,只是,链表中有多个哨兵对象POOL_BOUNDARY。删除时,内部的也就是后创建的pool先删除,一直删除到创建pool插入的POOL_BOUNDARY的位置,表示内部的pool删除完毕

objc[2012]: ##############
objc[2012]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[2012]: 17 releases pending.
objc[2012]: [0x101803000]  ................  PAGE  (hot) (cold)
objc[2012]: [0x101803038]  ################  POOL 0x101803038
objc[2012]: [0x101803040]       0x1005267b0  OBAnimal
objc[2012]: [0x101803048]       0x100526d40  OBAnimal
objc[2012]: [0x101803050]       0x1005265c0  OBAnimal
objc[2012]: [0x101803058]       0x10050de20  OBAnimal
objc[2012]: [0x101803060]       0x100524b40  OBAnimal
objc[2012]: [0x101803068]       0x100524b50  OBAnimal
objc[2012]: [0x101803070]       0x100524b60  OBAnimal
objc[2012]: [0x101803078]       0x100524b70  OBAnimal
objc[2012]: [0x101803080]       0x100524b80  OBAnimal
objc[2012]: [0x101803088]       0x100524b90  OBAnimal  //外部的pool开始删除
objc[2012]: [0x101803090]  ################  POOL 0x101803090 //内部的pool销毁
objc[2012]: [0x101803098]       0x100524ba0  OBAnimal //内部的pool删除介绍
objc[2012]: [0x1018030a0]       0x100524bb0  OBAnimal
objc[2012]: [0x1018030a8]       0x100524bc0  OBAnimal
objc[2012]: [0x1018030b0]       0x100524bd0  OBAnimal
objc[2012]: [0x1018030b8]       0x100524be0  OBAnimal //内部的pool开始删除
objc[2012]: ##############

一个自动释放池只能匹配一个线程,一个线程可以有多个释放池;子线程没有自己的AutoReleasePool,里面的对象使用主线程的RunLoop创建的AutoReleasePool,除非在线程内部创建新的AutoReleasePool;

二、RunLoop和AutoReleasePool的关系

系统通过@autoreleasepool {}这种方式来为我们创建自动释放池的,一个线程对应一个runloop,系统会为每一个runloop隐式的创建一个自动释放池,所有的autoreleasePool构成一个栈式结构,在每个runloop结束时,当前栈顶的autoreleasePool会被销毁,而且会对其中的每一个对象做一次release。

运行项目,打个断点,在控制台输出po [NSRunLoop currentRunLoop]看看当前的NSRunLoop里面有什么?

observers = (
    "<CFRunLoopObserver activities = 0x1, callout = _wrapRunLoopWithAutoreleasePoolHandler ",
    "<CFRunLoopObserver activities = 0xa0, callout = _wrapRunLoopWithAutoreleasePoolHandler"
)

我发现了两个观测者(其实还有很多其他观测者,我没有写出来),这两个观测者想干嘛?他们在观察什么?

先看看这两个观测者观察的那些状态:activities = 0x1,activities = 0xa0,对应的是0x1 = kCFRunLoopEntry, 0xa0 = kCFRunLoopBeforeWaiting | kCFRunLoopExit

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), 1
    kCFRunLoopBeforeTimers = (1UL << 1), 2
    kCFRunLoopBeforeSources = (1UL << 2), 4
    kCFRunLoopBeforeWaiting = (1UL << 5), 32
    kCFRunLoopAfterWaiting = (1UL << 6), 64
    kCFRunLoopExit = (1UL << 7),        128
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

原来这两个观测者是观察RunLoop的进入,休眠,退出的状态时,回调到_wrapRunLoopWithAutoreleasePoolHandler函数执行相应的操作。

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

第一个Observer

监听RunloopkCFRunLoopEntry事件,然后通过回调_wrapRunLoopWithAutoreleasePoolHandler执行objc_autoreleasePoolPush()

第二个Observer

监听了Runloop的两个状态kCFRunLoopBeforeWaitingkCFRunLoopBeforeExit
当收到kCFRunLoopBeforeWaiting时,调用回调_wrapRunLoopWithAutoreleasePoolHandler执行objc_autoreleasePoolPop(),然后在执行objc_autoreleasePoolPush()
当收到kCFRunLoopBeforeExit时,回调objc_autoreleasePoolPop()

结论

在AutoReleasePool中的对象什么时候释放?

自动释放池具有延迟释放的特性;当我们创建了一个对象,并把他加入到了自动释放池中时,他不会立即被释放,会等到一次runloop结束或者作用域超出{}或者超出[pool release]时被释放。

环境对象释放时刻
MRCObserver收到RunLoop进入休眠或者是退出的回调时,会执行objc_autoreleasePoolPop()操作,释放对象
ARC在当前作用域结束时,就会释放,因为编译器自动在作用域尾部添加了[obj release]
[pool release]自动释放池结束时
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // code...
    
    //手动释放 自动释放池时,自动释放池会对加入他中的对象做一次release操作
    [pool release];
    

以下代码分别在MRC和ARC环境下运行


- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s",__func__);
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"%s",__func__);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%s",__func__);
    NSLog(@"111");
    Animal *animal = [[[Animal alloc] init] autorelease];
    NSLog(@"222");
}

MRC

收到回调后才会释放,在viewWillAppear后,RunLoop就回去进入休眠,就会通知Observer,执行objc_autoreleasePoolPop()释放对象,然后重新objc_autoreleasePoolPush(),为什么还要push呢?因为要为下一次循环做准备。

Test_AutoReleasePool[22706:13757539] -[ViewController viewDidLoad]
Test_AutoReleasePool[22706:13757539] 111
Test_AutoReleasePool[22706:13757539] 222
Test_AutoReleasePool[22706:13757539] -[ViewController viewWillAppear:]
Test_AutoReleasePool[22706:13757539] -[Animal dealloc]
Test_AutoReleasePool[22706:13757539] -[ViewController viewDidAppear:]

ARC

作用域结束就会释放,因为编译器自动在作用域尾部添加了[obj release]

Test_AutoReleasePool[22808:13772253] -[ViewController viewDidLoad]
Test_AutoReleasePool[22808:13772253] 111
Test_AutoReleasePool[22808:13772253] 222
Test_AutoReleasePool[22808:13772253] -[Animal dealloc]
Test_AutoReleasePool[22808:13772253] -[ViewController viewWillAppear:]
Test_AutoReleasePool[22808:13772253] -[ViewController viewDidAppear:]

三、release和autorelease

release 底层可能会调用objc_msgSend(this, SEL_dealloc);方法,也就是可能会释放该对象。
autorelease是将该对象交给AutoreleasePool管理。

也就是说:在MRC环境下,一个对象没有调用autorelease,那么该对象不会由AutoreleasePool管理,由开发者自己调用[obj release]去释放对象,如果不调用[obj release]那么该对象就算写在自动释放里面也不会释放

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        OBAnimal *cat = [[[OBAnimal alloc] init] autorelease];
        cat.name = @"Tom";
        
        OBAnimal *dog = [[OBAnimal alloc] init];
        dog.name = @"jack";
        //[dog release]; //不调用release,则不会释放
    }
    return 0;
}

打印如下

2020-07-24 11:36:42.323129+0800 Tom call -[OBAnimal dealloc]
Program ended with exit code: 0

autorelease最终会执行到 AutoreleasePoolPage::add(obj)方法。也就是说会将对象交由AutoreleasePool自动释放池管理。

 static inline id *autoreleaseFast(id obj) {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

在ARC环境下。编译器会将我们的对象进行管理。

-临时变量非临时变量
ARC在作用域尾部插入release:[dog release]在对象创建时插入autorelease:[[[OBAnimal alloc] init] autorelease]

autorelease 会将该对象加入到离他最近的AutoreleasePool中,所以在子线程中可以加入新的自动释放池,这样可以快速释放对象。
以下代码如果不加入新的autoreleasepool,那么对象无法释放,因为RunLoop的保活,子线程不会退出,子线程的NSRunLoop没有注册observer,没有回调,对象无法释放。只有创建新的autoreleasepool就会将该对象放入新的池中,池子销毁他就会销毁。

dispatch_async(dispatch_queue_create("ob", DISPATCH_QUEUE_CONCURRENT), ^{
        for (int i = 0 ; i< 10; i ++) {
            @autoreleasepool {
                Animal *animal = [[[Animal alloc] init] autorelease];
                animal.name = @"dog";
            }
        }
        
        @autoreleasepool {
            Animal *animal = [[[Animal alloc] init] autorelease];
            animal.name = @"person";
        }
       
        [[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
});

由于系统会为每一个runloop隐式的创建一个自动释放池

子线程子线程创建了NSRunLoop子线程没有创建NSRunLoop
对象对象加入到currentRunLoop创建的autoreleasepool中对象加入到主线程的RunLoop创建的autoreleasepool中
对象释放由于子线程中autoreleasepool没有注册observer,所以只有等到autoreleasepool release时才能释放线程结束时,子线程中autoreleasepool执行[pool release],释放对象
结论

autoreleasepool释放对象的两个时刻:

  1. autoreleasepool向NSRunLoop注册了observer回调,那么runloop休眠或者退出时,清理池子
  2. autoreleasepool执行到[pool release]时,里面的对象执行[obj release]并回收池子

注意:系统会为每一个runloop隐式的创建一个自动释放池,只有执行[obj
autorelease]的对象,才会add到autoreleasepool中,这个对象才能被池子管理及释放。一个线程可以有多个池子,一个池子只能有一个线程。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值