ARC中的@autoreleasepool

Hello!小伙伴们,大家好呀!欢迎来到暴走…………(sorry,拿错剧本了)。咳咳,大家好呀!今天呢,我要来介绍一个非常古老的东西——Objective-C中的自动释放池。

我们拿起Xcode默认生成的HelloWorld代码我们就能够看到:

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        // start your code here...
    }
    return 0;
}
对于初学者来说,这个@autoreleasepool看起来实在是很费解啊。在实际的代码编写中,貌似无视掉(甚至去掉)这个@autoreleasepool好像也没什么影响嘛。那么这玩意到底是个神马?

这个问题呢,需要追溯到iOS 4以前,那个时候我们在使用Objective-C编写iOS和Mac OS X程序时,都是手动的进行内存管理,我们称其为:MRC(Mannul Reference Counting),内存管理机制为引用计数器,举一个例子(由于本文的重点不在于介绍MRC,因此只做概述不进行详细讲解):

@import Cocoa;

@interface Test : NSObject

@end

@implementation Test

- (void)dealloc {
    NSLog(@"析构");
    [super dealloc];
}

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"构造");
    }
    return self;
}

@end

int main(int argc, const char * argv[]) {
    Test *t = [[Test alloc] init]; // 构造,引用计数+1
    [t retain]; // 引用计数+1
    [t release]; // 引用计数-1
    [t release]; // 引用计数-1,归零,析构
    return 0;
}
如果创建的对象比较多,而它们又需要在一个统一的时间进行大量的release时,用这种方法管理内存就会使得代码过于冗长,降低可读性,因此,我们使用自动释放池来统一管理。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test *t1 = [[[Test alloc] init] autorelease]; // 构造,引用计数+1
        Test *t2 = [[[Test alloc] init] autorelease]; // 构造,引用计数+1
    } // 将所有调用过autorelease方法的对象全部释放,t1和t2析构
    return 0;
}
这里强调一个特殊情况,如果一个没有被调用autorelease方法的对象中途没有了指向的指针,则该对象在程序运行结束之前无法被释放,这样就会出现内存泄露。
int main(int argc, const char * argv[]) {
    Test *t1 = [[Test alloc] init];
    t1 = nil; // 指针脱离对象,对象无法再次被指向,因此无法被释放,产生内存泄露
    [[Test alloc] init]; // 同样产生内存泄露
    return 0;
}
但是,如果在指针脱离前,该对象处于自动释放池中,并且调用过autorelease方法,则不会出现这样的问题
@autoreleasepool {
        id t = [[[Test alloc] init] autorelease];
        t = nil;
        //......
    } // 在这里仍然会释放对象
这里搞明白以后我们再来看看ARC的情况。ARC把指向对象的指针分成了三类,分别是:strong,weak,autoreleasing。接下来将逐一介绍。

如果一个指针被声明为strong(__strong关键字为默认,可以省略),那么当这个这个指针指向某一对象时,该对象的引用计数器会+1,而当一个strong指针不再指向这个对象时,该对象的引用计数器会自动-1,请仔细观察以下的情况:

int main(int argc, const char * argv[]) {
    Test __strong *t1 = [[Test alloc] init]; // 创建对象,同时有strong指针指向对象,引用计数+1
    {
        Test __strong *t2 = t1; // 又有一个strong指针指向了对象,引用计数器+1
    } // 代码块结束,局部变量的生命周期结束,t2被销毁,对象少了一个strong指针去指向,引用计数-1
    t1 = [[Test alloc] init]; // t1指向了新的对象,原对象少了一个strong指针指向,引用计数-1,由于引用计数归零,对象析构
    t1 = nil; // t1清空,后创建的对象引用计数器-1,也被销毁
    return 0;
}
如果一个指针被声明为weak,那么该指针的指向不会影响对象的引用计数器,当weak指针指向的对象被释放时,该指针自动清空。这种类型的指针的出现主要是为了解决循环引用的问题(这里不多做阐述)
int main(int argc, const char * argv[]) {
    Test __strong *t1 = [[Test alloc] init]; // 创建对象,同时有strong指针指向对象,引用计数+1
    Test __weak *t2 = t1; // weak指针指向对象,引用计数器不变
    t1 = [[Test alloc] init]; // t1指向新对象,原对象引用计数器-1,归零,释放,这时,t2指针将会自动被赋值为nil,ARC的这种处理机制可以避免野指针(也叫垂悬指针)
    [t2 discardEditing]; // 向一个空指针(注意这里不是野指针)发送消息,不会产生错误,也不会得到回应
    
    return 0;
}
如果一个指针被声明为autoreleasing,那么该指针指向的对象将会被注册到当前的自动释放池中(如果当前不在自动释放池中,则相当于一个普通指针,这种情况下通常会产生内存泄露),当自动释放池结束时,会对所有注册到该池的对象进行引用计数器-1的操作
int main(int argc, const char * argv[]) {
    id __weak t1; // 定义了一个weak指针
    id t = [[Test alloc] init]; // 定义个一个strong指针,并指向一个新创建的对象,引用计数器+1
    @autoreleasepool { // 定义了一个自动释放池
        id __autoreleasing t2 = t; // 定义了一个autoreleasing指针,并指向对象,引用计数器+1,并将对象注册到自动释放池中
        t1 = t2; // 用一个weak指针指向对象,引用计数器不变
        t2 = nil; // 这里将autoreleasing指针清空,对原对象的引用计数器没有影响,但是由于该对象已经被注册到自动释放池中,因此不会产生内存泄露
        t = nil; // strong指针清空,对象的引用计数器-1
    } // 退出自动释放池,对象的引用计数器-1,归零,释放,t1指针自动清空
    [t1 discardEditing]; // 此时的t1成为了nil,因此向其发送消息不会得到回复,也不会产生错误
    return 0;
}
相信看到这里,你应该明白这个神奇的@autoreleasepool是什么东西了吧。值得一提的是,虽然ARC机制极力避免出现内存泄露以及野指针的情况,但在某些情况下还是会出现,例如:
    id __autoreleasing t;
    @autoreleasepool {
        t = [[Test alloc] init];
    } // 对象在这里释放
    [t show]; // 由于t并不是weak型,因此会成为野指针,发送消息会引发致命错误
int main(int argc, const char * argv[]) {
    id __autoreleasing t = [[Test alloc] init]; // 由于t没有处于任何的自动释放池,因此该对象无法被释放,会产生内存泄露
    return 0;
}
最后要说的一点是,自动释放池本身也是一个代码块,其中定义的变量(包括指针)如果没有用static关键字修饰的话,都会在代码块结束时被释放,我们在使用时要小心
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 5;
    }
    NSLog(@"%d", a); // 这里会报错,因为自动释放池中定义的a在这里没有作用域
    return 0;
}

关于ARC中的@autoreleasepool今天就讲到这里啦。其实我本人前阵子也是一直在为它发愁,查了很多资料都没有正面阐述在ARC中自动释放池的作用,网上搜出来的也总是千篇一律,不解决问题,所以只能自己慢慢尝试,慢慢研究。今天把研究的成功写成博客,希望能够帮到更多像我一样的人。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章