autoreleasepool

autorelease简介:

autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里面的所有对象做一次release操作,

 使用autorelease有什么好处呢:不用再关心对象释放的时间,不用再关心什么时候调用release

autorelesspool写法:

1. NSAutoreleasePool*autoreleasePool = [[NSAutoreleasePoolalloc] init];

 Person *p = [[[Person alloc] init] autorelease];

 [autoreleasePool drain];  //池子销毁会给池子中所有对象发送一条release消息,NSAutoreleasePool对象不能retain, 不能autorelease

2. @autoreleasepool{// 创建一个自动释放池

Person *p = [[Personnew] autorelease];// 将代码写到这里就放入了自动释放池

}// 销毁自动释放池(会给池子中所有对象发送一条release消息),在arc下自动管理。

MRC下需要对象调用autorelease才会入池, ARC下可以通过__autoreleasing修饰符, 否则的话看方法名, 非alloc/new/copy/mutableCopy开头的初始化方法编译器都会自动帮我们调用autorelease方法.

autorelease pool堆栈结构

每一个autorelease pool创建都会将池子入栈,给池子发送drain或者release消息,该池子和其后创建的池子都将销毁

例子: 

    NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc]init];

     TestOne*one = [[[TestOnealloc]init]autorelease];

    NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc]init];

    TestTwo *two = [[[TestTwo alloc]init]autorelease];

    [pool1 drain];

    sleep(5); //阻碍pool的释放,因为函数执行完池子就会被销毁,即使不发送release或drain消息

输出:

TestTwo dealloc

TestOne dealloc

即使只使用了  [pool1 drain];one对象和two对象都能释放

autorelease pool 与 线程

每一个线程(包括主线程)都有一个NSAutoreleasePool栈. 当一个新的池子被创建的时候, push进栈. 当池子被释放内存时, pop出栈. 对象调用autorelease方法进入栈顶的池子中. 当线程结束的时候, 它会自动地销毁掉所有跟它有关联的池子.


autoreleasepool_第1张图片
线程中的自动释放池栈

autorelease pool 与 RunLoop

autoreleasepool_第2张图片
autorelease pool 与 RunLoop


程序运行 -> 开启事件循环 -> 发生触摸事件 -> 创建自动释放池 -> 处理触摸事件 -> 事件对象加入自动释放池 -> 一次事件循环结束, 销毁自动释放池.

在开始每一个事件循环之前系统会在主线程创建一个自动释放池, 并且在事件循环结束的时候把前面创建的释放池释放, 回收内存

苹果在主线程 RunLoop 里注册了两个 Observer:

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入睡眠) 和 Exit(即将退出Loop),

BeforeWaiting(准备进入睡眠)时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;

Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

NSAutoreleasePool何时释放? 

分两种情况:

1.手动加NSAutoreleasePool是在大括号结束释放

2.没手动加时在当前的runloop迭代时结束时候释放

小实验:

@property(nonatomic, weak)NSString *string_weak;

- (void)viewDidLoad {

    [super viewDidLoad];

    // 场景 1

    NSString *string = [NSString stringWithFormat:@"1234567890"];

    self.string_weak= string;

     NSLog(@"string: %@",self.string_weak);

    //场景 2

    //        @autoreleasepool {

    //            NSString *string = [NSString stringWithFormat:@"1234567890"];

    //        _string_weak = string;

    //        }

    //    NSLog(@"string: %@",_string_weak);

    // 场景 3

    //        NSString *string = nil;

    //        @autoreleasepool {

    //            string = [NSString stringWithFormat:@”1234567890”];

    //            _string_weak = string;

    //        }

 // NSLog(@"string: %@",self.string_weak);

}

- (void)viewWillAppear:(BOOL)animated {

    [superviewWillAppear:animated];

    NSLog(@"string: %@", self.string_weak);

}

- (void)viewDidAppear:(BOOL)animated {

    [superviewDidAppear:animated];

    NSLog(@"string: %@", self.string_weak);

}

输出:

场景一:

string: 1234567890

string: 1234567890

string: (null)

 分析:使用stringWithFormat,string对象加入当前runloop的自动释放池,此时string对象的引用为strong和autoreleasepool;当作用域结束后strong引用用消失但autoreleasepool的引用还在,所以在viewWillAppear方法里面string: 1234567890;viewDidAppear方法时autoreleasepool销毁,autoreleasepool的引用消失,此时string销毁为null

场景二:

 string: (null)

string: (null)

string: (null)

分析:string在autoreleasepool的作用域结束后释放

场景三:

string: 1234567890

string: (null)

string: (null)

分析:结合上面两个场景可以分析出


autoreleasepool原理:

//将oc代码翻译成c++代码

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

    @autoreleasepool{

    }

}

翻译为:

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

        {

                __AtAutoreleasePool __autoreleasepool; 

            }

    } //也就是生成了一个__AtAutoreleasePool类型的__autoreleasepool实例

 __AtAutoreleasePool是一个结构体:

struct __AtAutoreleasePool {

  __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush();}

  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}

  void* atautoreleasepoolobj;

}; //__autoreleasepool中含有一个atautoreleasepoolobj指针,并且初始化和销毁时分别调用了objc_autoreleasePoolPush()和Pop方法


已经有人在苹果的官方源码里找到了关于AutoreleasePool的底层实现NSObject.mm里,源码地址:https://opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm.auto.html

void*objc_autoreleasePoolPush(void){

if(UseGC) returnNULL;//如果使用垃圾回收机制

return AutoreleasePoolPage::push();

}

void objc_autoreleasePoolPop(void*ctxt){

    if(UseGC)return;// fixme rdar://9167170

    if(!ctxt)return; 

     AutoreleasePoolPage::pop(ctxt);

}

//也就是池子的创建和销毁根本上是调用了AutoreleasePoolPage的push和pop方法

autoreleasepool_第3张图片
AutoreleasePoolPage结构

        AutoreleasePoolPage中存放的是加入池子的oc对象

        magic 用来校验 AutoreleasePoolPage 的结构是否完整;

        next 栈顶,初始时在哨兵对象的下一个位置,每插入一个元素next都会是这个元素的下一个位置

        begain 代表AutoreleasePoolPage的开始位置

        end 代表AutoreleasePoolPage的结束位置,每个AutoreleasePoolPage会开辟4096字节内存

        thread 指向当前线程;

        parent 指向父结点,第一个结点的 parent 值为 nil ;

        child 指向子结点,最后一个结点的 child 值为 nil ;

        depth 代表深度,从 0 开始,往后递增 1;

        hiwat 代表 high water mark 。

一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,通过parent和child指针连接成链表,后来的autorelease对象在新的page加入。


autoreleasepool_第4张图片
某个线程的 autoreleasepool 堆栈的内存结构图

上图堆栈由三个 AutoreleasePoolPage 结点组成,第一个 AutoreleasePoolPage 结点为 coldPage() ,最后一个 AutoreleasePoolPage 结点为 hotPage(),总共有两个 POOL_SENTINEL (哨兵)。

首先我来介绍几个概念

1.POOL_SENTINEL(哨兵对象):

在每个自动释放池初始化调用 objc_autoreleasePoolPush 的时候,都会把一个 POOL_SENTINEL push 到自动释放池的栈顶,并且返回这个 POOL_SENTINEL 哨兵对象。

void * atautoreleasepoolobj = objc_autoreleasePoolPush();

 objc_autoreleasePoolPop(atautoreleasepoolobj);

上面的 atautoreleasepoolobj 就是一个 POOL_SENTINEL

2.push 操作

void *objc_autoreleasePoolPush(void){

    if (UseGC) return nil;

    return AutoreleasePoolPage::push();

}

static inline void *push(){

    id *dest = autoreleaseFast(POOL_SENTINEL);

    assert(*dest == POOL_SENTINEL);

    return dest;

}

一个 push 操作其实就是创建一个新的 autoreleasepool ,对应 AutoreleasePoolPage 的具体实现就是往 AutoreleasePoolPage 中的 next 位置插入一个 POOL_SENTINEL ,并且返回插入的 POOL_SENTINEL 的内存地址,在执行 pop 操作的时候作为函数的入参。

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

    }

}

autoreleaseFast 函数(将对象插入到池子)在执行一个具体的插入操作时,分别对三种情况进行了不同的处理:

.当前 page 存在且没有满时,直接将对象添加到当前 page 中,即 next 指向的位置;

.当前 page 存在且已满时,创建一个新的 page ,并将对象添加到新创建的 page 中;

.当前 page 不存在时,即还没有 page 时,创建第一个 page ,并将对象添加到新创建的 page 中。

3.[obj autorelease] 操作

- (id)autorelease {

    return ((id)self)->rootAutorelease();

}

id objc_object::rootAutorelease2(){

    assert(!isTaggedPointer());

    return AutoreleasePoolPage::autorelease((id)this);

}

AutoreleasePoolPage 的 autorelease 函数的实现对我们来说就比较容量理解了,它跟pool push 操作的实现非常相似。只不过pool push 操作插入的是一个 POOL_SENTINEL ,而 autorelease 操作插入的是一个具体的 autoreleased 对象。下面是AutoreleasePoolPage::autorelease代码:

static inline id autorelease(id obj){

    assert(obj);

    assert(!obj->isTaggedPointer());

    id *dest __unused = autoreleaseFast(obj);

    assert(!dest  ||  *dest == obj);

    return obj;

}

4.pop操作

同理,前面提到的 objc_autoreleasePoolPop(void *) 函数本质上也是调用的 AutoreleasePoolPage 的 pop 函数

void objc_autoreleasePoolPop(void *ctxt){

    if (UseGC) return;

    // fixme rdar://9167170

    if (!ctxt) return;

    AutoreleasePoolPage::pop(ctxt);

}

pop 函数的入参就是 push 函数的返回值,也就是 POOL_SENTINEL 的内存地址,就是pool token 。当执行 pop 操作时,内存地址在 pool token 之后的所有 autoreleased 对象都会被 release 。直到 pool token 所在 page 的 next 指向 pool token 为止。

总结:每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,当需要release时,objc_autoreleasePoolPop(哨兵对象)作为入参,根据传入的哨兵对象地址找到哨兵对象所处的page。在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置

代码实战:

对象的autorelease方法会将当前对象加入栈顶的NSAutoreleasePool;NSAutoreleasePool是栈式管理,先入栈的后出栈,对NSAutoreleasePool对象发送release或者drain会销毁栈顶的NSAutoreleasePool对象一直到销毁当前的NSAutoreleasePool对象

子线程的autoreleasepool?

每个线程创建的时候就会创建一个autorelease pool,并且在线程退出的时候,清空autorelease pool。所以子线程的autorelease对象,要么在子线程中设置runloop清除,要么子线程结束时清除

TestObject:

+ (instancetype) instanceWithNumber:(NSInteger)number{

    //不加__autoreleasing 会因为arc返回值优化而不是一个autorelease对象,可以去掉试试

    __autoreleasing TestObject*object = [[TestObject alloc]init];

    return object;

}

-(void)dealloc{

    NSLog(@"Test Object dealloc");

}

ViewController:

  __weak id _testObject;

  NSThread*_testThread;

- (void)viewDidLoad {

    [super viewDidLoad];

    _testThread = [[NSThread alloc] initWithTarget:self

                                          selector:@selector(testBuild)

                                            object:nil];


    [_testThread start];

    [self performSelector:@selector(testAlert) onThread:_testThread withObject:nil waitUntilDone:NO];

}

- (void)testBuild{

    TestObject*testObject = [TestObjectinstanceWithNumber:10];

    _testObject= testObject;

}

- (void)testAlert{

    NSLog(@"test alert");

    NSLog(@"test alert");

    NSLog(@"test alert");

}


autoreleasepool_第5张图片

如何详细的查看堆栈信息?

在 Debug Navigator 下面有个搜框, 旁边有三个按钮, 把第一个按钮点击一下, 取消选中就可以了.

你可能感兴趣的:(autoreleasepool)