JHHK

欢迎来到我的个人网站
行者常至 为者常成

自动释放池

目录

自动释放池介绍

一、介绍

以下摘自苹果官方文档:
在引用计数(相对于垃圾回收)环境中,自动释放池包含了接受autorelease消息的对象, 当池子倾倒的时候,会对里面的每一个对象发送release消息。
因此对一个对象发送autorelease消息而不是release消息,可以增加对象的生命周期直到它所在的AutoreleasePool被倒掉。

通俗解释:
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。
在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

二、底层代码

原始代码

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

    }
    return 0;
}

执行clang命令后转为c++代码

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

    /* @autoreleasepool */ { 
        __AtAutoreleasePool __autoreleasepool; 
    }
    return 0;
}

__AtAutoreleasePool 代码结构

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

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

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

三、关键类AutoreleasePoolPage

1、AutoreleasePoolPage对象

调用了autorelease的对象,最终都是通过AutoreleasePoolPage对象来管理的

AutoreleasePoolPage的数据结构如下

每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址

所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起

magic_t const magic; // 用来校验 AutoreleasePoolPage 的结构是否完整 16字节
__unsafe_unretained id *next; // 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向begin() 8字节
pthread_t const thread; // 指向当前线程 8字节
AutoreleasePoolPage * const parent; // 指向父结点,第一个结点的parent值为nil 8字节
AutoreleasePoolPage *child; // 指向子结点,最后一个结点的child值为nil 8字节
uint32_t const depth; // 代表深度,从0开始,往后递增1 4字节
uint32_t hiwat;  // 代表high water mark 最大入栈数量标记 4字节

2、两个重要方法

每当调用objc_autoreleasePoolPush即AutoreleasePoolPage::push()方法时,会将POOL_BOUNDARY放到当前page的栈顶,并且返回这个边界对象。

而在调用objc_autoreleasePoolPop即AutoreleasePoolPage::pop()方法时,又会将边界对象以参数传入,这样自动释放池就会向释放池中对象发送release消息,直至找到第一个边界对象为止。

自动释放池查看

苹果提供了一个调试函数:_objc_autoreleasePoolPrint,可以用来打印自动释放池的创建信息

extern void _objc_autoreleasePoolPrint(void); // 需要用extern修饰才能在外部访问

int main(int argc, const char * argv[]) {
    @autoreleasepool {// 此处调用push()方法,返回边界对象1,并记录
        NSObject *objc = [[[NSObject alloc] init] autorelease];
        NSLog(@"objc:%@",objc);

        @autoreleasepool {// 此处调用push方法,返回边界对象2,并记录
            NSObject *objc2 = [[[NSObject alloc] init] autorelease];
            NSLog(@"objc2:%@",objc2);

            _objc_autoreleasePoolPrint();
        } // 此处调用pop(边界对象2)

    } // 此处调用pop(边界对象1)

    return 0;
}

输出结果如下

2021-09-20 21:54:23.661658+0800 KCObjcBuild[16800:319949] objc:<NSObject: 0x101a58ab0>
2021-09-20 21:54:23.663090+0800 KCObjcBuild[16800:319949] objc2:<NSObject: 0x1006282b0>
objc[16800]: ##############
objc[16800]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[16800]: 4 releases pending.
objc[16800]: [0x102016000]  ................  PAGE  (hot) (cold)
objc[16800]: [0x102016038]  ################  POOL 0x102016038
objc[16800]: [0x102016040]       0x101a58ab0  NSObject
objc[16800]: [0x102016048]  ################  POOL 0x102016048
objc[16800]: [0x102016050]       0x1006282b0  NSObject
objc[16800]: ##############

// 0x102016038 减去 0x102016000 的大小正好是56个字节    
// 第一个哨兵对象:0x102016038边界对象    
// 第二个哨兵对象:0x102016048边界对象

autorelease 对象在什么时候释放

一、系统释放

@implementation SSLPerson

- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    SSLPerson *person = [[[SSLPerson alloc] init] autorelease];
    NSLog(@"%s", __func__);
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"%s", __func__);
}
@end

运行结果:
-[ViewController viewDidLoad]
-[ViewController viewWillAppear:]
-[SSLPerson dealloc]
-[ViewController viewDidAppear:]

可以看到,调用了autorelease方法的person对象并没有在viewDidLoad方法结束后释放,而是在viewWillAppear方法结束后才释放.
说明在viewWillAppear方法结束的时候,调用了pop()方法释放了person对象,其实这是由RunLoop控制的.
下面来讲解一下RunLoop和@autoreleasepool的关系。

RunLoop 与 @autoreleasepool

kCFRunLoopEntry:
在即将进入RunLoop时,会自动创建一个__AtAutoreleasePool结构体对象,并调用objc_autoreleasePoolPush()函数。

kCFRunLoopBeforeWaiting:
在RunLoop即将休眠时,会自动销毁一个__AtAutoreleasePool对象,调用objc_autoreleasePoolPop()。 然后创建一个新的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPush()。

kCFRunLoopBeforeExit:
在即将退出RunLoop时,会自动销毁最后一个创建的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPop()。

所以,在iOS工程中系统干预释放的autorelease对象的释放时机是由RunLoop控制的,会在当前RunLoop每次循环结束时释放。
以上person对象在viewWillAppear方法结束后释放,说明viewDidLoad和viewWillAppear方法在同一次循环里。

二、手动释放

@implementation SSLPerson

- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    @autoreleasepool {
        SSLPerson *person = [[[SSLPerson alloc] init] autorelease];
    }
    NSLog(@"%s", __func__);
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"%s", __func__);
}
@end

运行结果:
-[SSLPerson dealloc]
-[ViewController viewDidLoad]
-[ViewController viewWillAppear:]
-[ViewController viewDidAppear:]

可以看到,手动添加到指定的@autoreleasepool中的autorelease对象,在@autoreleasepool大括号结束时就会释放了,不受RunLoop控制。

autorelease 对象详解

一、非autorelease对象

使用alloc/new/copy/mutableCopy生成的对象都不是Autorelease

//创建对象,引用计数为1    
NSMutableArray *array = [[NSMutableArray alloc] init];

自己生成的对象自己持有,相当于下面的mrc代码

//创建对象
NSMutableArray *array = [[NSMutableArray alloc] init];
//自己持有对象,在arc下这件事编译器帮我们做了
[array retain];

// array的引用计数为1   

二、autorelease对象

非alloc/new/copy/mutableCopy生成的对象都是Autorelease

// 引用计数为2
NSMutableArray *array = [NSMutableArray array];

相当于下面的mrc代码

+ (id)array {
    id obj = [[NSMutableArray alloc] init];//创建对象
    [obj retain];
    [obj autorelease];//延迟释放对象(谁创建谁释放)
    return obj;
}

NSMutableArray* array = [NSMutableArray array];
[array retain];

// 所以引用计数为2  

三、自动释放池应用

- (IBAction)autoreleasepoolUse:(id)sender {
//    int num = 10000;
    int num = 10;
    for (int i = 0; i < num; i++) {
        //如果没有使用autoreleasepool内存会暴涨,
        //暴涨的原因是img是autorelease对象,它会被放入自动释放池不会立即释放,在runloop休眠的时候才会释放,所以内存在短时间内会暴涨
        //使用一个手动创建的autoreleasepool时,离img最近的自动释放池就是我们手动创建的这个
        //img会被放入这个自动释放池,在离开作用域的时候会立即释放,所以内存不会暴涨
        __weak UIImage *wimg = nil;
        @autoreleasepool {
            UIImage *img = [UIImage imageNamed:@"XYAppDog"];
            NSLog(@"img = %@",img);
            
            // 如果没有 __weak UIImage *wimg = nil; 引用计数是2
            // 如果有 __weak UIImage *wimg = nil; 引用计数是3,这个理解不了啊????
            NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(img)));
            wimg = img;
        }
        NSLog(@"wimg = %@",wimg);// wimg = (null) 也就是说在这里img已经被释放掉了
    }
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.