Automatic Reference Counting 自动计数机制

很少有 Swift 开发者会担心内存管理这件事,这并不是事出偶然。在 Objective-C 里,学习程序的运行生命周期,是一件很重要的事情,而正是由于 Objective-C 这么多年来的发展,才让今天 Swift 开发者,不用担心这件事。

不管是 Objective-C 还是 Swift,所用的内存管理系统都叫做自动计数机制(Automatic Reference Counting它),它的简称是 ARC。如果你想要搞懂它的话,还是有必要知道它的历史的,我会尽可能简短地讲这部分内容。

一直以来,Objective-C 都是使用计数机制来管理内存的。当你创建了一个对象的时候,对应的计数就会变成 1,当你把它保存到另一个位置的时候,它的计数就会变成 2。而你在第一个位置把它释放了之后,计数就变回了 1;当你在它的第二个位置把它释放了之后,计数回到 0,也就意味这这个对象被销毁了。

Objective-C 诞生的这么多年来,它要求开发者使用一种叫「记忆管理」(manage memory)的方式来管理内存:retain 表示「添加引用」,release 表示「移除引用」,而 autorelease 表示「未来的某一个时刻移除引用」。如果你「持有」(retain)某一对象,一直都没有释放它的话,那么内存就会泄漏;或者当你释放了某个对象的时候,而它计数实际上在被释放之前已经是 0 了,那程序也会马上崩溃。尽管必须要承认,我还挺享受手动计数的,但确实一不下心的话,它就会造成内存泄漏。

所以区分 release 和 autorelease 就非常重要了。哪怕光看名字就可以看出来,autorelease 的意思是:「我会创建一个对象,但是不会一直用到它,所以等一下不用这个对象的时候,就帮我把它自动释放了」。在用循环的时候,经常会用到 autorelease,这个我们等一下再讲。首先你要明确的使用情景是:当你写了一个方法,这个方法内部必须要创建一个对象,并且需要传递这个对象的时候,就要用 autorelease 来管理内存。

那么问题来了,为什么要这么做呢?在之前我们肯定看到过这样的例子:

NSArray *array1 = [[NSArray alloc] initWithObjects:@1, @2, @3, nil];
NSArray *array2 = [NSArray arrayWithObjects:@1, @2, @3, nil];

这两者之间的区别在于:array1 会有一个为 1 的引用计数,而 array2 同样也会有为 1 的引用计数,但是会在调用结束之后自动释放。

我记得我自己在学这部分的时候也是非常蛋疼,你怎么知道 arrayWithObjects 运行之后就自动释放了呢?好吧,这个是因为苹果是有一整套自己的命名方式。当你调用一个名字里带有 alloc, new, copy, 或者 mutableCopy 的方法,就默认要持有(retain)这个对象。而上面的 array2 用了 arrayWithObjects,说明了你并不想持有这个对象,它就会被自动释放。

到目前为止,我们讲的这些东西可能还不是太麻烦,甚至有点儿像无聊的日常,但至少这部分是有逻辑的。接下来事情就会变得比较搞了(要是你跟我一样比较奇怪的话,也会觉得这部分比较有意思)。

当你声明了一个 retain 属性,也就是现在的 strong 属性,它的计数会被自动设为 1。然后你用 [[NSArray alloc] init...] 初始化一个同样计数为 1 的数组,接着你会发现,retain 属性的计数变成了 2,接着内存就泄漏了。是不是有毒 (/>_<)/~┴┴

所以你会经常看到下面这种代码:

self.someProperty = [[[SomeClass alloc] init] autorelease];

更糟糕的是,retain 这个特性,仅在你使用这个属性的时候有效。如果你直接读取属性的实例变量,那么这个实例变量就不再是 retain 的了,属性会变成一个僵尸 —— 实例对象已经被释放而不存在了,但这个属性还在这里。如果你真的这么做了,结果就是对象等一下被释放,而发送的消息鬼知道会发到哪里去。相信我,这样真的会造成很多问题的。

results matching ""

    No results matching ""