Autoreleasepool的理解及原理

Autoreleasepool的理解及原理

之前师父问我对autoreleasepool有什么理解?当时的并没有研究过autoreleasepool,只知道在程序启动时会建立一个autoreleasepool,但不清楚autoreleasepool运行的原理。那么今天就来总结一波。

接下来将从以下几个问题出发。

1、什么时候,什么对象会加入autoreleasepool;
2、autoreleasepool的创建与销毁;
3、autoreleasepool的原理;


一、什么对象什么时候加入autoreleasepool?

autorelease 对象在创建时会被加入autoreleasepool中。

那么什么是autorelease对象呢?

    如果不是通过alloc、new、retain、copy 创建出来的对象都是autorelease对象。

    比如:"NSString *str = [NSString stringWithFormat:@"xiaoyueyue"],类方法创建的对象str为autorelease对象。

    如果是通过alloc、new、retain、copy 创建出来的对象怎么进行处理?(通过arc内部进行引用计数来进行管理的,此问题待深究)

二、autoreleasepool的创建与销毁

autoreleasepool的创建与销毁可分为两种情况:

  1. 系统自动创建与销毁

    自动创建:

    在进入程序入口时系统会在main函数中自动创建autoreleasepool。

    这里写图片描述

    自动销毁:

    (1).自动创建的autoreleasepool会在runloop交替的间隔中销毁,autoreleasepool释放池中对象。

    (2).在autoreleasepool被撑满时销毁,autoreleasepool释放池中对象。

    runloop什么时候会发生交替呢?

    从程序启动到加载完成再到等待用户交互,此过程是一个完成的runloop。当用户再次进行交互时会重新启动一个新的runloop。这里理解的比较浅显了,待深入之后再详细说明

  2. 手动创建与销毁

    手动创建:通过@autoreleasepool{}方法创建autoreleasepool.

    举个栗子,在for循环中创建大量临时变量时,手动创建autoreleasepool来管理for循环产生的变量,如下:

    int largeNum = 1024 * 1024 *2;
    for (int i = 0; I < largeNum; I ++ ){
    @autoreleasepool{
        NSString *str = [NSString stringWithFormat:@"hello"];
        str = [str uppercaseString];
        str = [NSString stringByAppendingFormat:@"World"];
    }
    NSLog(@"this is test");
    }
    

    手动创建autoreleasepool的销毁

    autorelease对象在什么时候释放取决于autoreleasepool什么时候销毁,autoreleasepool什么时候销毁又取决于autoreleasepool的作用域,

    autorelease对象是在autoreleasepool所在作用域执行结束时释放的,autoreleasepool所在作用域执行结束,autoreleasepool被销毁,然后释放池中对象。

    原来以为,autorelease对象在@autoreleasepool{}执行完立即释放,但是这种想法是有问题的。比如说在上图for循环中的autoreleasepool,@autoreleasepool{}执行完成时,autoreleasepool并没有释放,当NSlog()执行结束,本次循环结束下一次循环开始时,即autoreleasepool所在作用域执行结束时,autoreleasepool被销毁,autoreleasepool释放池中对象。

    思考:程序入口已经为我们创建autoreleasepool,我们自己为什么还要创建autoreleasepool呢?

    举个栗子,在上图(手动创建autoreleasepool)的for循环中,每次for循环中创建的字符串str对象都是不同的(可以通过查看str的内存地址来验证NSLog(@”%p”,str)),所有在for循环中的创建字符串对象需要等到循环结束时才会被释放。
    这时就会消耗大量的内存资源,存在内存问题。这时就需要我们自己来创建autoreleasepool了。

    主线程或者是GCD中的线程,这些线程默认都有 autoreleasepool,除此之外,在主线程外开启子线程时,需要我们手动创建autoreleasepool来管理autorelease对象。因为主线程的的runloop默认是开启的,子线程的runloop默认是关闭的,也就意味着子线程中没有autoreleasepool,子线程中的autorelease对象就没有办法进行释放,这就会造成内存泄漏。

三、自动释放池的原理

autoreleasepool 以一个队列数组的形式实现,主要通过下列三个函数完成.

  1. objc_autoreleasepoolPush
  2. objc_autoreleasepoolPop
  3. objc_autorelease

对 autorelease 分别执行 push,和 pop 操作。销毁对象时执行release操作。

上面的push和pop两个函数都是对AutoreleasePoolPage的简单封装

AutoreleasePoolPage又是什么?AutoreleasePoolPage是一个C++实现的类,结构如下。

这里写图片描述

  • AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
  • AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
  • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
  • 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
  • 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入

所以,若当前线程中只有一个AutoreleasePoolPage对象,并记录了很多autorelease对象地址时内存如下图:

这里写图片描述

图中的情况,这一页再加入一个autorelease对象就要满了(也就是next指针马上指向栈顶),这时就要执行上面说的操作,建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈顶添加新对象。

所以,向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置

———

释放的时候来了

———

每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),那么这一个page就变成了下面的样子:
这里写图片描述
objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参,于是:

  1. 根据传入的哨兵对象地址找到哨兵对象所处的page
  2. 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
  3. 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page

刚才的objc_autoreleasePoolPop执行后,最终变成了下面的样子:

这里写图片描述

以上总结摘抄自几个不同的博客,多数是sunnyxx的博客,很感谢大神们的分享,我理解的差不多的时候,也总结出来再做一次分享,同样也是方便自己复习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值