Objective-C的AutoreleasePool与Runloop的关联

注释AutoreleasePool的源码在runtime/NSObject.mm中可以找到。

相信很多人都知道,autorelease就是让对象延迟释放,很多人还更具体的以为是在出了作用域释放,也有的说是出了作用域后某个时间点,但是具体是在啥时候呢,也不清楚。


那具体如何呢,我们还是要到源码里才能一探究竟:

我们先新建一个项目,并跟踪一个对象的内存情况,我们可以看到如下截图内容,在objc_destructInstance(对象销毁)调用前,实际是执行了autoreleasePoolPage的pop方法,如图:



那为啥不是autoreleasePool的 pop呢?autoreleasePoolPage又是什么东西呢?


我们可以通过代码转换的方法来看:

@autoreleasepool {
        NSString *str = @“test”;
    }

通过clang rewrite方法,转换成c++后,就变成:

void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);

那么objc_autoreleasePoolPush()做了什么的,我们回到源码(NSObject.mm):

void *

objc_autoreleasePoolPush(void)

{

    if (UseGC) return nil;

    return AutoreleasePoolPage::push();

}

因此可以看出,autoreleasePool在编译后实际便是AutoreleasePoolPage。

再看Push的实际方法:

    static inline void *push() 

    {

        id *dest = autoreleaseFast(POOL_SENTINEL);

        assert(*dest == POOL_SENTINEL);

        return dest;

    }


这段代码很简单,也就是主要执行了autoreleaseFast(POOL_SENTINEL);

这段代码其实就是往AutoreleasePoolPage里插入了一个nil指针,我们在这可以认为nil就是插入的一个旗手、标记:

如下图,车队1全部车入库后,插入一个标记,车队2入库后再插入一个标记。。。。。



当管理员收到指令,说把最后入库的车队出库的时候,管理员就从最后一台车开始往外出库,直到遇到一个标记,就知道最后一个入库的车队已经全部出库完了。

上图我们只是用了四个车队,十多个车来描述,假设我有几万个车队的几十万台车,或则几亿的车要入库,那一个停车场肯定也放不下,那我们就需要多个停车场,如果有多个停车场,那就存在一个车队被分在两个或多个停车场的头尾部分,为了保证一个车队都是按序的出库,那我们就得知道车库之间的顺序,那我们就可以在车库里放一个本子,来标记当前车库的前一个车库在哪里,当前车库的下一个车库在哪里等信息。


上面这个案例便是autoreleasePoolPage的原型了。实际autoreleasePoolPage是一个双向链表,一个page可以认为是一个车库,这个page的“停车数量”就是一个内存页的大小(4096b),这个page里也有那么一个“本子”,记录上一个page在哪,下一个page在哪,如下图:


其中:

magic 用来校验 AutoreleasePoolPage 的结构是否完整;
next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() ;
thread 指向当前线程;
parent 指向父结点,第一个结点的 parent 值为 nil ;
child 指向子结点,最后一个结点的 child 值为 nil ;
depth 代表深度,从 0 开始,往后递增 1;
hiwat 代表 high water mark 。


回到代码中,当我们新建一个pool的时候,就是往page中加入了一个标记X,在pool退出的时候,执行pop,把最后一个autorelease的对象到标记X这个范围的的所有对象执行release。


我们这里举得例子是自己手动添加的情况:

@autoreleasepool {//在这里会执行push
        NSString *str = @“test”;
    }//代码执行到这里会执行pop

那如果不是手动添加的情况,会啥时候添加标记和清除标记呢,比如以下代码:

- (void)viewDidLoad {//会在这里push么?

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

    NSString * str = nil;

    str = [NSString stringWithFormat:@"%@",[NSDate date]];

}//会在这里pop么?


其实这里不会再括号开始的时候push,也不会在括号结尾的地方pop,这时候我们就得看runloop的了。


Runloop我们不做详细介绍,感兴趣的话点击这里,Runloop工作流程图如下:


如图,App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。

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


第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。


从上面图我们可以看出,runloopd的2->9,9->2循环工作,我们把2->9,9->2认为是一次迭代,称为一个loop。


从上面observer工作便知,每一个loop,便会有一次pop和push,因此我们得出:

1、如果手动添加autoreleasePool,autoreleasePool作用域里的自动释放对象会在出pool作用域的那一刻释放;

2、如果非手动添加的,那么自动释放的对象会在每一次runloop循环结束时,释放当前这次循环所创建的自动释放对象。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值