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

作者:Andyy Hope,原文链接,原文日期:2016-02-23
译者:wiilen;校对:小锅;定稿:CMB

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

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

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

什么是引用计数?

引用计数是计算机科学中的一种技术,通过这种技术,每个对象在实例化时都被分配了一个计数值,因此应用程序可以知道哪些对象仍在使用。在对象的生命周期中,其它对象如果需要使用该对象,就声明对该它的所有权,然后增加计数值;当该对象完成了任务之后释放(release)所有权,然后减少计数值。一个对象的计数值减为 0 时,就会从内存中销毁(deallocate)。举个例子:

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

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

在看了上面的代码之后,你的第一印象可能觉得这么做并没有什么大不了,因为它很简单。然而当代码量持续增加,并且更多开发者参与到项目中时,开发者更有可能在这里出错。

手动引用计数

所以 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 1.1 什么是自动引用计数 概念:在 LLVM 编译器中设置 ARC(Automaitc Reference Co...
    __silhouette阅读 5,076评论 1 17
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,946评论 1 16
  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,470评论 1 3
  • 以下是关于内存管理的学习笔记:引用计数与ARC。iOS5以前自动引用计数(ARC)是在MacOS X 10.7与i...
    啊左阅读 4,545评论 0 17
  • 荆棘花冠 我想要一顶荆棘花冠 长满刺的花冠戴在头上 鲜血顺着眼睛流下 每根刺刺向每颗心 这顶花冠要放在清晨的祭坛上...
    陈清伟阅读 557评论 2 2