iOS 与 OSX 内存管理:引用计数

做者:Andyy Hope,原文连接,原文日期:2016-02-23
译者:wiilen;校对:小锅;定稿:CMBios

在 2009 年,我第一次下定决心要学习如何开发 App。那时候 iOS 3 才刚刚发布,以后,App Store 就成了那些开发 to-do 列表、笔记记录以及其它无聊应用的开发者们的金矿。git

Objective-C 是我决定要全身心投入学习的第一门面向对象的语言,那时候这门语言与如今有不少区别。过去几年咱们见证了它的发展,与此同时苹果还发布了使人印象深入的 Swift 语言。github

现在开发者们认为 ARC(自动引用计数)是理所应当的存在,特别是那些在 iOS 5 发布(2011年)以后学习 Objective-C 的人,或是学习 Swift 的人。objective-c

什么是引用计数?

引用计数是计算机科学中的一种技术,经过这种技术,每一个对象在实例化时都被分配了一个计数值,所以应用程序能够知道哪些对象仍在使用。在对象的生命周期中,其它对象若是须要使用该对象,就声明对该它的全部权,而后增长计数值;当该对象完成了任务以后释放(release)全部权,而后减小计数值。一个对象的计数值减为 0 时,就会从内存中销毁(deallocate)。举个例子:swift

objc
- (void)demonstration {
    MyClass *foo = [[MyClass alloc] init];
    [foo performSomeMethod];
    [foo release];
}

在上面的代码中,咱们作了三件事:初始化一个对象,调用它的某个方法,最后释放它。在 iOS 5 发布以前,开发者们在开发应用时都须要这么作。这种内存管理的方式被称为手动引用计数,即 MRC(Manual Reference Counting) 或 MRM(Manual Reference Management)。app

在看了上面的代码以后,你的第一印象可能以为这么作并无什么大不了,由于它很简单。然而当代码量持续增长,而且更多开发者参与到项目中时,开发者更有可能在这里出错。函数

手动引用计数

因此 MRC 中具体包含了哪些内容呢?学习

alloc

objc
MyClass *foo = [[MyClass alloc] init];

这段代码是 Objective-C 中最最基础的。要建立一个对象,首先须要初始化它。当调用 alloc 来建立一个对象时,系统会为该对象分配内存空间,并将它的引用计数设为 1。翻译

release

objective-c
[foo release];

对象调用 release 会使它的引用计数减 1。当对象的引用计数减为 0 时,系统会将该对象从内存移除,并释放内存空间以供其它对象使用。指针

retain

objc
[foo retain];

对象调用 retain 时,会通知系统为它的引用计数加 1。调用 retain 意味着其它对象想要持有 foo

咱们假设两个不一样的对象都持有 foo,当第一个对象对 foo 调用 release 时,foo 的引用计数会从 2 减为 1。第二个对象仍然可使用 foo,而无需担忧 carsh 或产生一个悬空指针。

copy

objc
MyClass *bar = [foo copy];

copyretain 的原理很类似,它能够复制一份原来的对象,不一样之处在于引用计数。若是复制 foo 时它的引用计数为 4,复制获得的对象 bar 的引用计数只会为 1。

autorelease

objc
[foo autorelease];

当一个对象的做用域超出了它所声明的范围,就须要对其调用 autorelease。它会告诉系统,咱们并不但愿当即销毁这个对象,而是在 autoreleasepool 被清空的时候再去销毁这个对象。

autorelase 一般当咱们在一个方法内部声明一个对象并将其返回给其调用者时使用。另外一个情景是对象在 for 循环中实例化,而且该循环中有一个 autoreleasepool 时,也可使用 autorelease

objc
- (MyClass *)foo {
    MyClass *foo = [[MyClass alloc] init];
    [foo autorelease];
}

autoreleasepool

objc
- (void)example {
    for (int i = 0, i < 10, i++) {
        @autoreleasepool {
            MyClass *foo = [[MyClass alloc] init];
            [foo autorelease];
        }
    }
}

上面的代码中,咱们实例化了 foo 对象,并对其调用了 autorelease。这些操做被包裹在 autoreleasepool 中,外层还有一个 for 循环。

这么作的好处在于,for 循环中实例化的全部对象,能够在每一轮循环结束时自动被释放。当这一切发生时 autoreleasepool 会本身进行销毁,并释放全部对象,恢复到原来干净整洁的状态。

dealloc

objc
- (void)dealloc {
    [foo release];
    [bar release];
    [fubar release];
    [super dealloc];
}

dealloc 是全部继承自 NSObject 的对象最后会调用的方法。你能够把 dealloc 想象成清理那些引用计数大于 0 的遗留对象的地方。

手动引用计数的缺点

如今你应该能更好的理解手动管理引用计数所须要作的工做。下面介绍两个常见场景,关于微小的人为失误可能致使运行时 crash 的状况。

悬空指针

objc
MyClass *foo = [[MyClass alloc] init];
[foo release];
[foo doSomething];

以前提到过,若是对象的引用计数减为 0,系统会将该对象从内存移除。该地址的内存空间清空以后,可能保留着仍为空的状态,也可能有其它对象占据了这块空间。

可是要记住一点, foo 指针仍然指向这块内存。因此当 doSomething 方法被调用时,实际上会让 nil 或其它占据这块内存的对象去调用这个方法,这样作经常会引发 crash。

内存泄漏

objc
MyClass *foo = [[MyClass alloc] init];
[foo retain];
[foo retain];
[foo release];

这有点像悬空指针的反面状况,当对象调用 release 的次数少于调用 retain 的次数时,就会发生内存泄漏。若是对象的引用计数一直不减为 0,系统就没法把该对象的资源分配给其它对象。

你的应用中有些对象永远没有被释放,看上去好像没有多大的问题,不过若是这样的对象太多,就会致使应用的内存被耗光,产生一些奇怪的问题,并最终致使 crash。这也是咱们在 dealloc 中执行清空操做的缘由。

关于自动引用计数

Session 323 - iOS,OS X

2011 年,旧金山六月的一个使人愉快的早晨,你们在 Hall-H 参加了每一年一度的 WWDC。Phil Schiller 展现了新的 Mail app,Scott Forstall 展现了 Game Center 的漂亮 UI,不幸的是那也是 Steve Jobs 的最后一场 keynote?。那场 Keynote 中展现了大量新内容,但没有 ARC 这个对开发者而言的大惊喜。这周晚些时候,开发者们也参加了关于 iOS 与 OS X 的 Session 323,他们的生活今后发生了巨大的变化。

自动化的魔法

直到今天,全部 iOS 与 OS X 应用仍然使用引用计数,惟一不一样在于咱们再也不须要进行手动管理,由于编译器都帮咱们作好了。

这里严肃声明一点,全部我以前提到的语句,编译器在编译时会帮咱们自动插入,包括 retainreleasecopyautoreleaseautoreleasepool。若是你回去看看我提供的演示代码,想象上面五种调用都被注释了,简而言之 ARC 都帮咱们处理好了。继续前进吧,初学者们,自由地写 app,不用再担忧内存管理!除了循环引用以外你不须要再担忧什么,不过那是下一节的内容了。


我已经在 GitHub 中上传了一些样例,你能够下载看看其中内存管理的语法。这只是一个简单的 OS X 控制台程序,其中 ARC 已经关闭了。示例函数的调用已经都被注释了,若是你想要看看它们的运行效果,只须要取消注释,编译并运行。尽情体验吧!

本文由 SwiftGG 翻译组翻译,已经得到做者翻译受权,最新文章请访问 http://swift.gg

相关文章
相关标签/搜索