收藏iosiOS plus基础应用

iOS原理 理解AutoreleasePool释放对象的时机

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

iOS原理 文章汇总

在介绍AutoreleasePool基本概念的时候提到,AutoreleasePool在销毁时,会统一给池中的所有对象发送一次release消息。通过这个机制,我们可以立即或者延迟释放对象。

延迟释放

Runloop创建的AutoreleasePool,只会在Runloop即将休眠或退出的时候销毁,这时候池中的对象才会被释放,因此可以达到延迟释放的效果。这里以主线程Runloop为例,看看主线程中的对象被延迟释放的情况。

如图所示,直接在viewDidLoad方法里下断点,运行后在控制台进行bt输出,结果如下:

在输出结果里可以看到,此时主线程Runloop已经创建了一个AutoreleasePool

这里创建两个UIImage对象,其中一个是自动释放对象,另一个为非自动释放对象,以作对比。

//弱引用指针不会持有对象,不影响对象的引用计数
__weak UIImage *_img1 = nil;
__weak UIImage *_img2 = nil;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    //创建两个UIImage对象
    UIImage *img1 = [[UIImage alloc] init];            //非自动释放对象,不能被添加到AutoreleasePool
    UIImage *img2 = [UIImage imageNamed:@"test.png"];  //自动释放对象,会被添加到AutoreleasePool
    _img1 = img1;
    _img2 = img2;
    //打印两个对象的地址
    NSLog(@" ==== %@:img1 = %p, img2 = %p", NSStringFromSelector(_cmd), _img1, _img2);
}

-(void)viewWillAppear:(BOOL)animated{
    
    [super viewWillAppear:animated];
    //打印两个对象的地址
    NSLog(@" ==== %@:img1 = %p, img2 = %p", NSStringFromSelector(_cmd), _img1, _img2);
}

-(void)viewDidAppear:(BOOL)animated{
    
    [super viewDidAppear:animated];
    //打印两个对象的地址
    NSLog(@" ==== %@:img1 = %p, img2 = %p", NSStringFromSelector(_cmd), _img1, _img2);
}

//打印结果
==== viewDidLoad:img1 = 0x600003274990, img2 = 0x6000032705a0
==== viewWillAppear::img1 = 0x0, img2 = 0x6000032705a0
==== viewDidAppear::img1 = 0x0, img2 = 0x0

从打印结果可知,_img1viewDidLoad方法结束时就被释放了,而_img2被延迟释放了。这是因为:

因此,程序运行后,主线程Runloop会自动创建AutoreleasePool,在主线程中创建的自动释放对象都会被添加到AutoreleasePool中,这些对象都会被延迟释放。

立即释放

使用@autoreleasepool {}代码块手动创建的AutoreleasePool,当超出代码块的作用域时被销毁,这时会释放池中的对象,可以达到立即释放的效果。一般情况下,在编写循环时可能需要手动创建AutoreleasePool

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

    @autoreleasepool {

        UIImage *img = [UIImage imageNamed:@"test.png"];
        NSLog(@" ==== %p", img);
    }
}

运行后可以看到,在循环过程中内存并不会上涨。这是因为
每次迭代都会创建并销毁一个AutoreleasePool,每次创建的UIImage对象都会被添加到AutoreleasePool,在销毁时就被释放了,所以内存不会上涨。

因此,在循环内使用自动释放池块可以在下一次迭代之前释放这些临时对象,这样就可以降低内存峰值。需要注意的是,如果循环内都是非自动释放对象,就不需要手动创建AutoreleasePool,即使创建了也起不了任何作用。

基于降低内存峰值的需求,苹果自己也在下面这几个方法里封装了@autoreleasepool {},遍历时使用这几个方法性能更好。

  • enumerateObjectsUsingBlock
  • enumerateObjectsWithOptions
  • enumerateObjectsAtIndexes

推荐阅读

iOS原理 AutoreleasePool的基本概念

上一篇下一篇

猜你喜欢

热点阅读