Objective-C 内存管理

写在前面

本文是阅读 Advanced Memory Management Programming Guide 的笔记。

主要内容是关于手动管理内存的规则。

众所周知,Objective-C 它提供了2种内存管理方式:

  1. Manual Retain-release MRR
  2. Automatic Reference Counting ARC

目前 Xcode 默认使用 ARC ,而在 ARC 环境下,很多工作,编译器已经帮忙完成了。

而要真正了解内存管理规则,还得追根溯源,从 MRR 出发。

简介

内存管理可能出现的问题

  • 释放或重写正在使用的内存数据,一般会造成应用闪退,更严重地,弄脏用户数据。
  • 没有释放已经不再使用的内存,即造成 memory leaks。

内存问题检测工具

Xcode 附带的静态分析工具,可以分析出可能有问题的地方。

如果解决了静态分析工具找到的问题后,仍然有内存管理问题,可考虑使用下述工具或技术来定位问题:

  1. 官方调试技巧,尤其是其中的 NSZombie,可以找回已经释放了的对象。
  2. 使用 Instruments 去追踪引用计数情况,以及定位内存泄漏。

内存管理规则

主要是使用 NSObject 相关的方法 retain, release, dealloc 进行管理。

基本规则

  • 自己创建的对象,自己持有
  • 非自己创建的对象,也能持有
  • 释放不再需要的某个对象
  • 不能释放未持有的对象

自己创建的对象,自己持有

当使用以 alloc, new, copy, mutableCopy 开头的方法,创建对象时,持有该对象。

给某个对象发 retain 消息后,也能持有它

一般会在2种情况下使用 retain

  • 在 init 方法中,将某个参数作为实例变量
  • 避免某个对象因其他操作而被销毁。

使用 autorelease 来延迟发送 release 消息

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
    return string;
}

在上述代码中,因为 string 是由 alloc 方法生成的,所以你持有它,当方法结束后,你不再需要它,所以必须在方法结束前将其释放。

如果使用 release,那么在方法结束前,string 就被销毁了,根本无法返回。

所以只能使用 autorelease 来延迟释放它。

没有持有只是返回引用的对象

NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
                        encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
    // Deal with error...
}
// ...
[string release];

因为 error 不是你创建的,所以你没有持有它,也就不需要释放它。

覆写 dealloc 去释放持有的对象

不能直接调用 dealloc 方法。

在 dealloc 方法里,不要试图去释放稀有资源,如网络、缓存等。

在 MRC 环境,需要给实例变量发送 release 消息,最后需要调用 [super release]

Core Foundation 使用类似,但稍微不一样的规则。

内存管理详细介绍

使用 Accessor Methods 让内存管理更容易

Accessor Methods 即常说的 Getter 和 Setter 方法

'get' accessor

- (NSNumber *)count {
    return _count;
}

'set' accessor

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}

如果你的类有一个属性是个对象,不妨假设为 P,它由另外一个对象 A 赋值得到,那么你必须保证 P 在使用过程中,A 不会被销毁。

所以你必须持有 A,并且在合适时机释放 A,但这很容易忘记,无疑会增加出问题的概率。

不要在 Initializer Methods 和 dealloc 中使用 Accessor Methods

正确的方式,应该像下面代码,直接赋值,不要使用 Setter 方法。

- init {
    self = [super init];
    if (self) {
        _count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

使用弱引用来避免循环引用

如果一个对象收到 retain 消息,那么将会有一个强引用指向它。

一个对象只有在没有任何强引用时,即引用计数为0,才能被销毁。

当2个对象直接或间接地强引用对方时,它们之间存在一个引用循环。

因为都存在强引用,除非在其中对象之一的内部,自动释放对另一对象的引用,否则两者都无法被销毁。

常见的情况就是使用 Block。

避免正在使用的对象被销毁

有些情况下,对象会被自动销毁

  1. 当从一个 collection 中移除时。
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.

当一个对象从 collection 中,比如 NSArray,被移除时,它会收到 release 消息,而不是 autorelease 消息,如果该 collection 是这个对象的唯一持有者,那么这个对象就会被销毁。

若想避免这种情况,需要对从 collection 中获取的对象,发送 retain 消息,这样就能持有它,当不需要时,再释放。

  1. 当『父对象』被销毁时
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.

如上所示,对象 heisenObject 是从对象 parent 中获得,当 parent 被销毁时,如果 parent 是 heisenObject 的唯一持有者,那么 heisenObject 也会被销毁,相当于在 parent 的 dealloc 方法中,调用了 [heisenObject Release]

不要在 dealloc 中管理『稀有』资源

『稀有』资源有文件描述符、网络连接、缓冲、缓存等。

dealloc 何时被调用并不明确,有可能会被延时,也有可能是一步步执行的,甚至可能因为一个 bug 而造成应用闪退时,就被调用了。

collection 持有它们包含的对象

collection 有 array, dictionary, set 等等,如果一个对象被加入到 collection 时,该对象会调用 retain 方法,那么 collection 持有该对象。

当对象从 collection 中被移除时,该对象会被发送 release 消息。

持有规则的实现靠的是引用计数

  • 当你创建一个对象时,它的引用计数为1。
  • 当给一个对象发送 retain 消息时,它的引用计数加1。
  • 当给一个对象发送 release 消息时,它的引用计数减1。
  • 当给一个对象发送 autorelease 消息,它的引用计数会在当前 autorelease pool block 结束时减1。
  • 当一个对象的引用计数为0时,它会被销毁。

使用 Autorelease Pool Blocks

@autoreleasepool {
    // Code that creates autoreleased objects.
}

如上述代码所示
在 autorelease pool block 即将结束的时候,它当中那些收到过 autorelease 消息的对象,会被发送 release 消息。

即只要一个对象收到过 autorelease 消息,在当前 autorelease pool block 即将结束时,这个对象就会收到 release 消息。

autorelease pool block 可以互相嵌套,但比较少用。

Cocoa 希望代码都在一个 autorelease pool block 中,否则自动释放的对象不会被释放,这样就会有内存泄漏。

如果你在一个 autorelease pool block 外面发送 autorelease 消息,那么 Cocoa 将会报错。

AppKit 和 UIKit 的每次事件,事实上,都是运行在一个 autorelease pool block 中

事件指的是像一次点击这样的事件。

所以一般不需要自己创建一个 autorelease pool block,但也有一些情况例外:

  1. 如果你写的程序,不是基于 UI Framework 的,比如说 command-line tool。
  2. 如果你在每次循环里,创建了大量临时对象,那么最好在循环里创建一个 autorelease pool block,来降低应用的内存峰值。
  3. 如果你创建了多条线程,那么在线程开始时,你最好创建自己的 autorelease pool block。

在 Cocoa 应用中的每条线程,都包含独立的 autorelease pool block 栈,如果是多线程开发,务必创建自己的 autorelease pool block。

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

推荐阅读更多精彩内容