内存管理篇(一)

理解引用计数

前言: OC 语言使用引用计数来管理内存,也就是说, 每个对象都有个可以递增或递减的计数器. 如果想使某个对象继续存活, 那就递增其引用计数; 用完了之后, 就递减其引用计数. 当计数变为0时, 对象就会被销毁了.

ARC实际上就是一种引用计数机制.

要点

1. 引用计数机制通过可以递增递减的计数器来管理内存. 对象创建好之后, 其保留计数至少为1. 若保留计数为正, 则对象继续存活. 当保留计数降为0时, 对象就被销毁了.

2. 在对象生命周期中, 其余对象通过引用来保留或释放此对象. 保留与释放操作分别会递增或递减保留计数.

1.引用计数工作原理

在引用计数架构下, 对象有个计数器, 用以表示当前有多少个事物想令此对象继续存活下去. 在OC中, 叫"保留计数"(retain count), 也可以叫"引用计数"(reference count).

NSObject协议声明了以下三个方法用于操作计数器,以递增或递减其值:

  • retain : 递增保留计数
  • release : 递减保留计数
  • autorelease : 待稍后清理"自动释放池"(autorelease pool)时, 再递减保留计数.

查看保留计数的方法: retainCount, 这方法不太管用,在调试时也是如此,苹果公司并不建议使用

对象创建出来时,其保留计数至少为1. 若想令对象继续存活,则调用retain方法. 若不再使用此对象, 就调用release或autorelease方法. 当保留计数为0时,对象就被回收了, 系统会将其占用的内存标记为"可重用". 此时,所有指向该对象的引用就无效了.

在对象生命周期中, 其保留计数时而递增, 时而递减, 最终归零

应用程序在其生命周期中会创建很多对象, 这些对象都相互联系着. 例如下图中: 对象B与对象C 都引用了对象A. 若对象B与对象C都不再使用对象A, 则其保留计数减为0, 于是对象A被销毁了.

对象图里所有指向对象A的引用均释放之后, 对象A所占内存亦可回收

图中还有其他对象引用对象B与对象C, 而应用程序里又有另外一些对象引用其他对象. 如果按"引用树"回溯,最终会发现一个"根对象". 在iOS应用程序中, 是UIApplication对象. Max OS中则是NSApplication对象,两者都是应用程序启动时创建的单例.

下面这段代码有助于理解这些方法的用法:

NSMutableArray *array = [[NSMutableArray alloc] init];
    
NSNumber *number = [[NSNumber alloc] initWithInt:2019];
    
[array addObject:number];
[number release];
    
// do something with 'array'
    
[array release];

在ARC下, 无法编译release方法; 在OC中, 调用alloc方法所返回的对象由调用者拥有. 对象引用计数加1, 注意: 这并不是说对象此时的保留计数必定是1. 在alloc 或 initWithInt: 方法实现代码中, 也许还有其他对象引用了此对象, 所以其保留计数可能会大于1. 能够肯定的是: 其保留计数至少为1. 保留计数的概念应该这样理解, 绝不应该说保留计数一定是某个值, 只能说你执行的操作递增或递减了该计数.

创建完数组后, 把number对象加入其中, 调用数组的 addObject: 方法时, 数组也会在 number上调用 retain 方法, 来继续保留该对象. 这时计数至少为2. 接着释放 number 对象, 保留的计数至少为1, 调用 release 之后, 已经无法保证所指的number 对象是否存活, 也就不能正常使用 number变量了. 当前本例中,数组还引用着number对象, 在调用release之后依然存活, 然而绝不应假设此对象一定存活, 也就是说不能像下面那样编码:

[number release];
NSLog(@"number= %@", number);

如果由于某些原因, 其引用计数减为0, 那么number对象所占内存也许会被回收, 此时调用 NSLog 可能会使程序崩溃!!!

为避免不经意间使用了无效对象, 一般用完release 之后都会清空指针. 保证不会出现可能指向无效对象的指针, 这种指针通常称为"悬挂指针".
可以照下面编码来防止此情况发生:

[number release];
number = nil;

2.属性存取方法中的内存管理

访问属性时, 会用到相关实例变量的获取方法及设置方法. 若属性为"strong"关系, 则设置的属性值会保留.
例: 有个名叫 foo 的属性由名为 _foo 的实例属性所实现, 那么,该属性设置方法会是这样的:

- (void)setFoo:(id)foo {
    [foo retain]; // 保留新值
    [_foo release]; // 释放旧值
    _foo = foo;
}

此方法将保留新值并释放旧值, 然后更新实例变量, 令其指向新值. 顺序很重要, 假如还未保留新值就先把旧值释放了, 而且两个值又指向同一个对象, 那么, 先执行release操作就可能导致系统将此对象永久回收. 而后续的retain操作无法令这个已经回收的对象复生, 于是实例变量也就成了悬挂指针.

3. 自动释放池

在OC的引用计数架构中, 自动释放池是一个重要特性. 调用release 会立刻递减对象的保留计数(而且可能令系统回收此对象), 有时候可以改为调用 autorelease, 此方法会在稍后递减计数, 通常是在下一次"事件循环"时递减, 不过也可能执行更早些.

此特性很有用, 尤其在方法中返回对象时; 以下面方法为例:

- (NSString *)stringValue {
    NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@",self];
    return str;
}

此时返回的str对象其保留计数比期望值多1, 因为调用alloc会令保留计数加1, 而又没有与之对应的释放操作. 有retain,就应该有对应的release, 将其抵消. 这并不是说保留计数就一定是1, 可能大于1(这取决于initWithFormat:方法内部实现).

但是,不能在方法内释放str,否则还没等方法返回, 系统就把该对象回收了. 这里应该用autorelease, 它会在稍后释放对象, 从而给调用者留下足够长时间在其需要时先保留返回值. 也可以这样理解: 此方法可以保证对象在跨越"方法调用边界"后仍然存活.

- (NSString *)stringValue {
    NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@",self];
    return [str autorelease];
}

修改之后,stringValue方法把NSString对象返回给调用者时,此对象必然存活. 所以能像下面使用:

NSString *str = [self stringValue];
NSLog(@"the string is %@",str);

由于返回的str对象将于稍后自动释放,多出来的那一次retain操作到时自然就会抵消,无需再执行内存管理操作. 因为自动释放池中的释放操作要等到下一次事件循环时才会执行.

autorelease能延长对象生命期,使其在跨越方法调用边界后依然可以存活一段时间.

4. 循环引用

使用引用计数机制时, 经常要注意一个问题就是: 循环引用; 也就是呈环状相互引用的多个对象. 这将导致内存泄漏, 因为循环中的对象其保留计数不会降为0.

通常采用"弱引用"来解决此问题, 或是从外界命令循环中的某个对象不再保留另外一个对象. 这两种方法都能打破保留环, 避免内存泄漏.

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

推荐阅读更多精彩内容