iOS初学者

对于autoreleasePool的一些思考

2019-04-06  本文已影响0人  上北以北

以下考虑均基于ARC

顾名思义 autoreleasePool 是一种自动的内存管理机制,它的工作机制很简单,就是把需要自动管理的对象放到 autoreleasePool 中,待 autoreleasePool 结束后把池子中的对象释放掉。

首先补充说明一点,在OC中 自生成并持有对象的方式只有 alloc/new/copy/mutableCopy 四种 ,其他方式均为非自生成并持有对象 如下代码

    {
        //自生成并持有对象
        NSMutableArray *array1 = [[NSMutableArray alloc]init];
        
        //非自生成并持有对象, 因为array2 持有的是通过 +()array 方法返回的对象
        NSMutableArray *array2 = [NSMutableArray array];
    }

接着autoreleasePool说,正常情况下,在超出作用域时对象会被自动释放掉,如下代码

    {
        NSObject *obj = [[NSObject alloc]init];
        //自己生成并持有对象,引用计数 + 1 (retain)
        //retainCount = 1
    }
    //超出作用域 obj 引用计数 -1 (release)
    //此时retainCount = 0 所以 obj 销毁

retain 和 release 成对出现,不需要另加一个自动释放池来进行管理,一样能完成内存的自动回收。这不就扯了吗?本来就好好的东西,干嘛非得加个自动释放池呢?看下面的一个例子

- (NSObject *)getObj {
    NSObject *obj = [[NSObject alloc]init];
    //自己生成并持有对象,引用计数 + 1 (retain)
    //retainCount = 1
    return obj;
    //return 导致提前出作用域 引用计数 -1 (release)
    //retainCount = 0 obj 被释放
}

当我们需要调用这个函数赋值时 如下

    {
        NSObject *obj_1 = [self getObj];
    }

问题来了,正如刚才所说,正常情况下 出作用域时对象会被自动释放掉,于是就造成了 obj_1 在想取得持有对象时 发现对象被释放掉了,这显然是不合理的。这就像是你满心欢喜在天猫买了个冰棒,拿到快递时发现冰棒竟然化没了,你说闹心不闹心。

虽然道理是这个道理,但在实际工作时并没有这种情况发生,这是怎么回事呢?这其实就是autoreleasePool的功劳了,编译器会在return 之前提前把对象retain 并 注册到自动释放池 大体过程类似下面的代码(这里只是用于演示过程)

- (NSObject *)getObj {
    NSObject *obj = [[NSObject alloc]init];
    //自己生成并持有对象,引用计数 + 1 (retain)
    //retainCount = 1
    
    //下面这一步由编译器自动完成
    NSObject *autoreleaseObj = obj;
    [autoreleaseObj retain];
    //obj 引用计数 + 1
    //retainCount = 2
    
    [autoreleaseObj autorelease];
    //把autoreleaseObj注册到自动释放池
    
    return autoreleaseObj;
    //return 导致提前出作用域 引用计数 -1 (release)
    //retainCount = 1 obj不会被释放
    //此时obj 被autoreleasePool持有
}

那这个pool在哪里呢?对于oc来说,整个程序都是运行在一个pool中的,可以看一下main函数的实现

int main(int argc, char * argv[]) {
    //自动释放池
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

此时由于return后对象被autoreleasePool持有,因此不会被提前释放掉,obj_1 自然也就可以拿到并持有该对象引用计数+1。这样,当出作用域时obj_1被释放,引用计数-1,当autoreleasePool退出时 对象引用计数 -1 至此被注册到autoreleasePool的对象的引用计数 = 0 被释放掉。由此完成了内存的自动管理。

问题是解决了,但会造成内存消耗增加,这就好比天猫的员工站出来说,为了保障你的冰棒半途不会化掉,你需要多加点钱我们给你配一个保温箱。

为什么会增加内存消耗?由于对象会被注册到自动释放池,而且在自动释放池结束前对象一直被持有,因此当大量的对象被注册到自动释放池时就会造成内存激增,看下面一段代码

  - (void)viewDidLoad {
    [super viewDidLoad];

    for (int i = 0; i < 10000; i ++) {
        for (int j = 0; j < 100; j ++) {
            NSMutableArray *array = [self getArray];
            NSLog(@"%@",array);
        }
    }
 }

- (NSMutableArray *)getArray {
    NSMutableArray *arr = [[NSMutableArray alloc]init];
    [arr addObject:@"hh"];
    return arr;
}

事实上,只要是调用非自己生成并持有的对象,该对象就会被注册到自动释放池,比如下面的方式

NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"hh", nil];
//由于调用的是非自己生成并持有对象,所以会被注册到自动释放池,因此在循环调用时一样会造成内存激增。需要注意

但有两个个例,NSString 和 NSNumber
调用下面方法时str不会被注册到自动释放池,因此不会造成内存增加,原因可能是NSString的alloc会被编辑器默认处理为常量(不太确定,有大神知道的话还请告知)

- (NSString *)getString {
    NSString *str = [[NSString alloc]initWithString:@"hh"];
    return str;
}

但是下面这样调用NSString会添加到自动释放池

NSString *str = [NSString stringWithFormat:@"哈哈哈"];
//取得非自己生成并持有对象

关于NSNumber 下面的两种方式都不会被注册到自动释放池(原因不明,有知道的大神还请告知)

NSNumber *num = [NSNumber numberWithInteger:15];
//取得非自己生成并持有对象

- (NSNumber *)mynum {
    NSNumber *n = [[NSNumber alloc]init];
    n = @(2);
    return n;
}

接着内存暴增说,既然自己动释放池会造成内存暴增,那肯定要找方法来解决,我们从自动释放池的释放过程入手来解决这个问题,首先说一下自动释放池的嵌套。

关于嵌套,这里补充一点,当多层自动释放池嵌套时,内层自动释放池会屏蔽掉外层自动释放池对内层自动释放池中的对象retain,很绕,说的直白一点,就是内层自动释放池中的对象只会注册到内层自动释放池中。如下

//外层池子
    @autoreleasepool {
        //内层池子
        @autoreleasepool {
            NSMutableString *poolStr = [NSMutableString stringWithString:@"hh"];
            //非自己生成并持有对象,poolStr被注册到内层池子
        }
        //内层池子结束,poolStr释放
    }

结合上面自动释放池内存激增的原理,既然内层池子释放时,注册到内层池子的对象也会被释放,因此对于内存激增的问题,我们可以采用自动释放池的嵌套来解决,对于上面的代码我们稍加修改,如下

 - (void)viewDidLoad {
    [super viewDidLoad];

    for (int i = 0; i < 10000; i ++) {
        //内层池子
        @autoreleasepool {
            for (int j = 0; j < 100; j ++) {
                NSMutableArray *array = [self getArray];
                NSLog(@"%@",array);
            }
        }
    }
 }

- (NSMutableArray *)getArray {
    NSMutableArray *arr = [[NSMutableArray alloc]init];
    [arr addObject:@"hh"];
    return arr;
}

此时,由于内层池子中的一百次循环完毕后,内层池子便会结束,因此注册到内层池子的对象便会被即时释放,因此内存不会继续增加。

关于autoreleasePool就先整理到这吧,有不对的地方还请大神们不吝赐教。

上一篇下一篇

猜你喜欢

热点阅读