iOS KitiOS学习收藏ios

iOS原理 AutoreleasePool的基本概念

2021-01-05  本文已影响0人  东篱采桑人

iOS原理 文章汇总

前言

一般情况下,对象在超出作用域时会立即release。比方说,在一个方法里创建一个局部对象:

-(void)test{
    
    NSObject *obj = [[NSObject alloc] init];
}

test方法执行完,这个NSObject对象就会被release了。但有些时候,比如从工厂方法返回对象时,并不希望对象在超出作用域后立即release,这就需要通过AutoreleasePool来实现。

AutoreleasePool的基本介绍

AutoreleasePool(自动释放池)是OC中的一种内存管理机制,它持有释放池里的对象的所有权,在自动释放池销毁时,统一给所有对象发送一次release消息。通过这个机制,可以延迟对象的释放。

UIImage *img = [UIImage imageNamed:@"xxxx.png"];

这个UIImage对象是在类方法imageNamed里创建完后再返回,对象的所有权归方法持有,如果不延迟释放,在方法结束时对象就被释放了,返回的就为nil。因此,需要将对象先加入AutoreleasePool,所有权归自动释放池持有,只有自动释放池销毁时才释放,这样UIImage对象才能在方法结束后正常返回。

AutoreleasePool的创建方式

通常使用@autoreleasepool {}代码块来手动创建一个自动释放池

@autoreleasepool {
    //这里创建自动释放的对象,创建的对象会被加入到AutoreleasePool对象里
    ... ...
}

这个代码块等价于

{
    //创建一个AutoreleasePool对象
    __AtAutoreleasePool *atautoreleasepoolobj = objc_autoreleasePoolPush(); 
    
    //这里创建自动释放的对象,创建的对象会被加入到AutoreleasePool对象里
    ... ...    

   //给所有自动释放的对象发送一次release消息,并销毁AutoreleasePool对象
   objc_autoreleasePoolPop(atautoreleasepoolobj)
}

`{}`表示AutoreleasePool对象的作用域

代码块的实现逻辑如下:

注意区分AutoreleasePool对象自动释放的对象AutoreleasePool对象指的是实例化的一个自动释放池(本质也是对象),而 自动释放的对象是指被加入到这个池中的对象。
AutoreleasePool的原理可阅读后面的底层分析一文。

AutoreleasePool在Runloop中的创建和销毁

通常情况下,在平时开发中不需要手动创建自动释放池,因为Runloop会自动创建和销毁AutoreleasePool对象。

如上图所示,AutoreleasePoolRunloop中的创建和销毁的过程如下:

也就是说,在一个RunLoop事件开始的时候会自动创建一个AutoreleasePool,在事件结束时再自动销毁。上面举例的imageNamed方法内部创建的对象也是加入到主线程RunLoop创建的AutoreleasePool中实现延迟释放的。因此,通常在开发中不需要开发者自己创建AutoreleasePool

手动创建AutoreleasePool的场景

虽然Runloop会自动创建和销毁自动释放池,但在有些情况下还是需要手动创建AutoreleasePool苹果官方文档建议在下面这三种情况下可能需要开发者创建自动释放池:

这里就第二个场景举例,来说明在循环内使用AutoreleasePool对于降低内存峰值的作用。

//情况一:循环内不使用AutoreleasePool
for (int i = 0; i<1000000; i++) {

    NSString *string = [NSString stringWithFormat:@"%@", @"0123456789"];
    NSLog(@" ==== %p", string);
}

//情况二:循环内使用AutoreleasePool
for (int i = 0; i<1000000; i++) {

    @autoreleasepool {

        NSString *string = [NSString stringWithFormat:@"%@", @"0123456789"];
        NSLog(@" ==== %p", string);
    }
}

分别运行上面两种情况可以看到,在循环过程中,第一种情况的内存占用一直在增加,第二种情况的内存不会增加。这是因为:

这个场景中AutoreleasePool是通过立即释放对象来降低内存峰值,而前面又说自动释放池用来延迟对象的释放,这两者其实不矛盾,本质是一样的,都是在自动释放池销毁时调用objc_autoreleasePoolPop来释放池中的对象。只不过调用的时机不同,这里的@autoreleasepool {}是在超出自己的作用域时就调用函数来销毁,而前面的是在Runloop休眠或退出时才调用函数来销毁,所以调用的时机不同,才会实现立即或者延迟释放的目的。

@autoreleasepool {}的作用域指的就是前面提到的{},是AutoreleasePool对象的作用域。

哪些对象可以被添加到自动释放池?

MRC模式下,只要给对象发送autorelease消息,这个对象就会被添加到自动释放池。但在ARC模式下,是由编译器自动给对象发送autorelease消息,且不会给所有的对象都发送,只会给被编译器识别为自动释放的对象发送。一般来说,使用类方法(工厂方法)实例化的对象才是自动释放的对象,才能被添加到自动释放池,而使用new、alloc、copy关键字生成的对象和retain了的对象,不会被添加到自动释放池中。

for (int i = 0; i<1000000; i++) {

    @autoreleasepool {

        //1.自动释放的对象,需要被添加到自动释放池中
        UIImage *image = [UIImage imageNamed:@"test.png"];
        
        //2.非自动释放的对象,不能被添加到自动释放池中
        UIImage *image = [[UIImage alloc] init];
        
        NSLog(@" ==== image = %p", image);
    }
}

分别运行上面两种情况,第一种情况内存不会增加,第二种情况内存会增加。第二种情况虽然在@autoreleasepool {}中创建对象,但由于不是自动释放的对象,所以还是不能被添加到AutoReleasePool中,只能在循环结束一起释放。因此,在ARC模式下,只有自动释放的对象才能被添加到AutoReleasePool中,非自动释放的对象在超出作用域时会被立即释放。

需要注意的是,自动释放的对象如果没有被添加到AutoReleasePool中,就会产生内存泄露。

总结

总得来说,关于AutoreleasePool的基本概念可以归纳以下几点:

上一篇 下一篇

猜你喜欢

热点阅读