Objective-C内存管理

释放掉不用的内存,保证还可能被使用的内存不会被回收。这是内存管理要做的的事情,OC是通过引用计数来管理的,MRC和ARC的区分只是:引用计数是由程序员还是编译器和语言来负责管理。

为啥要使用引用计数

在c中堆中的对象是由程序员负责的:

// malloc必须和free成对出现
char *str = (char*)malloc(sizeof(char)*10);

// do something

// 如果忘了free就泄漏了10个字节的内存,如果多次free,那将导致崩溃
free(str);

// 如果此时又使用了str,这块内存可能已经分配做其他用途了,所以行为是不确定的
// do not use str

看起来好像很简单,但是在do somethind的过程中可能经历里很多的,所以说保证这块内存的正确使用完全依靠程序员,有很大的风险,而且调试指针异常也是很麻烦的。

OC中的引用计数

// NSObject 提供的引用计数的功能
- (instancetype)retain OBJC_ARC_UNAVAILABLE;    // rc++
- (oneway void)release OBJC_ARC_UNAVAILABLE;    // rc--,若rc==0,则释放该实例对象
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;   // 延迟释放

苹果内存管理文档给出了使用引用计数的原则:

  1. 你拥有你创建的任何对象,调用命名上为“alloc”, “new”, “copy”, or “mutableCopy”的函数,将获得一个有所有权的实例对象(You own any object you create)
// 这个NSObject的实例对象属于obj,也就是obj拥有NSObject的实例对象
NSObject *obj = [[NSObject alloc] init];
  1. 可以使用retain获取对象的所有权
// 这个NSObject的实例对象属于obj,也就是obj拥有NSObject的实例对象
NSObject *obj = [[NSObject alloc] init];
// 通过retain方法,这个对象也属于obj1了
NSObject *obj1 = [obj retain];
  1. 当不再需要它时,必须放弃你拥有的对象的所有权
// 这个NSObject的实例对象属于obj,也就是obj拥有NSObject的实例对象
NSObject *obj = [[NSObject alloc] init];
// 通过retain方法,这个对象也属于obj1了
NSObject *obj1 = [obj retain];
// 通过release方法,放弃objc1对这个实例对象的所有权
[objc1 release];
  1. 不能放弃没有拥有权对象的所有权
// 根据1中的命名规则,str没有这个NSString实例对象的所有权
NSString *str = [NSString stringWithFormat:@"hello NSString"];

// 所以如下,释放一个没有所有权实例对象的所有权是错误的
[str release];

关于autorelease(延时释放)

//
- (id)myMethod {
    // obj 拥有 MyObject实例对象的所有权,引用计数为1
    MyObject *obj = [[MyObject alloc] init];
    // 如果按照上述苹果的内存管理原则3,你需要在return前release obj,但是如果release了就被回收了
    // 这就用到了autorelease 将对象延时释放
    return [obj autorelease];
}

autorelease的实现是要配合autoreleasepool来完成的,AutoreleasePoolPage是由一个双向链表实现的栈,每个节点一个虚页的大小。autorelease消息实际是将obj push到栈中,并在pop时进行release。

MRC与ARC

MRC:手动引用计数,需要我们手动控制拥有权,也就是负责发送retain release autorelease消息。
ARC:通过分析编译生成的语法树,编译器帮我们自动插入引用计数相关的代码。

ARC存在的问题和解决方法

考虑如下代码:

// ARC环境下
@interface Person : NSObject <NSMutableCopying, NSCopying>
@property (nonatomic, copy)NSString *name;
@property (nonatomic)Person *partner;
+(instancetype) personWithName: (NSString*)name;
@end

Person *xiaohong = [Person personWithName: @"xiaohong"];
Person *xiaoming = [Person personWithName: @"xiaoming"];
xiaohong.partner = xiaoming;
xiaoming.partner = xiaohong;

在上述代码中,xiaohongxiaoming如果没有其他的引用,那么最终他们的引用计数都为1,所以他们都不能被释放,又因为不能被释放的原因是xiaohong引用xiaoming导致xiaoming不能被释放,xiaoming引用xiaohong导致xiaohong不能被释放,如下图①,所以称为循环引用

循环引用示意图

如何解决循环引用呢,首先选择一方放弃对另一方的引用,例如xiaohong.partner = nil;,如上图②所示,此时没有对象引用xiaoming,所以释放,在xiaoming调用dealloc时将会放弃对xiaohong的引用,所以xiaohong也能得以正确释放。

使用weak来解决循环引用

在使用weak关键字:

// ARC环境下
@interface Person : NSObject <NSMutableCopying, NSCopying>
@property (nonatomic, copy)NSString *name;
@property (nonatomic, weak)Person *partner;
+(instancetype) personWithName: (NSString*)name;
@end

Person *xiaohong = [Person personWithName: @"xiaohong"];
Person *xiaoming = [Person personWithName: @"xiaoming"];
xiaohong.partner = xiaoming;
xiaoming.partner = xiaohong;

此时的情况如上图③所示,weak有并不会增加对象的引用计数,所以在xiaohongxiaoming可以顺利的被释放。

其实解决循环引用只需要要打破这个引用环就可以,即如图②所示, 图③所示只是一种充分非必要条件。

Core Foundation和ARC

ARC是针对于NSObject的一套方法,所以在Core Framework是不适用的,OC中的一些对象与Core Frameword又是toll-bridge(就是可以进行简单的相互转换)的,虽然结构可以相互转换,但是还需要考虑所有权的问题,即谁来负责release的工作。

// 即使是两种结构是toll-bridge的,我们也不能简单的类似于(NSString*)的指针强转,因为还有所有权问题(引用计数)
// cf与NSObject结构相互转化的关键字
__bridge    // NSObject <-> cf 不改变所有权
__bridge_retained   // NSObject -> cf   将所有权从ARC交给CF的引用计数   
__bridge_transfer   // cf -> NSObject   将所有权从CF的引用计数交给ARC

在实际使用的过程中,无非两种情况,内存管理最终交由谁处理呢?

1. 最终由ARC负责内存管理

// case 1 这应该是最常用的方式
    NSString *str = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef cfStr = (__bridge CFStringRef)str; // 不改变所有权,类似于(CFStringRef)str
    
// case 2 
    CFStringRef cfName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
    NSString *name = (__bridge_transfer NSString*)cfName;   // 将所有权从CF的引用计数交给ARC
    
// case 3   下面实际是发生了所有权变化,但经过步骤1,2最终又将所有权交给了ARC
    NSString *str = [[NSString alloc]initWithFormat:@"test"];  
    CFStringRef cfStr = (__bridge_retained CFStringRef)str; // 1. 将所有权从ARC交给CF的引用计数
    str = (__bridge_transfer NSString*)cfstr;  // 2. 将所有权从CF的引用计数交给ARC

2. 最终由CF的引用计数负责

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

推荐阅读更多精彩内容