【iOS夯实】内存管理之ARC的基本使用

什么是 ARC

ARC (Automatic Reference Counting) 是2011年 WWDC 中,苹果为了解决由于内存管理引起的 Crash 而提出的解决方案。
简单来说,ARC提供是一个编译器的特性,帮助我们在编译的时候自动插入管理引用计数的代码,帮助我们完成之前 MRC 需要完成的工作。

ARC的本质仍然是通过引用计数来管理内存。


ARC 的使用准则

  1. 不可以调用 dealloc , 不能调用或实现跟引用计数相关的方法(retain/release/autorelease/retainCount等),否则产生编译错误。
  2. 不可以使用 NSAllocateObjectNSDeallocateObject
  3. 在 C 结构体里不可以使用 Objective C 对象。
  4. 不能显式转换 idvoid *
    id 指的是 Objective C 对象,void * 指的是 C 指针(CGColorRef), idvoid * 之间赋值要添加 __bridge 系列关键字。
  5. 不可以使用 NSAutoreleasePool, 用 @autoreleasepool 代替。
  6. 不可以使用区域 (NSZone)。
  7. 不可以使用以 new 开头的属性名称。若使用会有以下的编译错误”Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects”

ARC 所有权修饰符

Objective-C 编程中为了处理对象,可以将变量类型定义为 id 类型或者各种对象类型。

对象类型就是指向 NSObject 这样的 Objective-C 类的指针,比如 NSObject *
id 类型用于隐藏对象类型的类名部分,类似 C 语言的 Void *

ARC 有效时,id 类型和对象类型和 C 语言其他类型不同,其类型上必须附加所有权修饰符。
所有权修饰符有 4 种

  • __strong 修饰符
  • __weak 修饰符
  • __unsafe_unretained 修饰符
  • __autoreleasing 修饰符

4种修饰符均可以保证附有这些修饰符的自动变量初始化为 nil

id __strong object1;
id __weak object2;

相当于

id __strong object1 = nil;
id __weak object2 = nil;

__strong 修饰符

__strong 修饰符是 id 类型和对象类型默认的所有权修饰符,也就是说 id 和对象类型在没有明确指定所有权修饰符的时候,默认为__strong

// 两行代码效果相同
id object = [[NSObject alloc] init];
id __strong object = [[NSObject alloc] init];

当 ARC 无效时

{
   id __strong object = [[NSObject alloc] init];
}

可记述为如下代码

{
   // ARC 无效时
   id __strong object = [[NSObject alloc] init];
   [object release];
}

为了释放生成并持有的对象,增加了调用 release 方法的代码。
如上面代码所示, __strong 修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

关注一下代码中关于对象的所有者的部分

{
   id __strong object = [[NSObject alloc] init];
}

该对象的所有者如下

{
   // 自己生成并持有对象。
   id __strong object = [[NSObject alloc] init];
   // 变量 object 为强引用,所以自己持有对象。
}
   // 变量 object 超出其作用域,强引用失效,自动释放自己持有的对象。
   // 对象的所有者不存在,因此废弃该对象。

取得非自己生成并持有的对象时

{
   id __strong object = [NSMutableArray array];
}

在 NSMutableArray 类的 array 类方法的代码中取得非自己生成并持有的对象

{
   // 取得非自己生成并持有的对象时。
   id __strong object = [NSMutableArray array];
   // 变量 object 为强引用,所以自己持有对象。
}
   // 变量 object 超出其作用域,强引用失效,自动释放自己持有的对象。
   // 对象的所有者不存在,因此废弃该对象。

附有 __strong 修饰符的变量之间可以相互赋值

id __strong object1 = [[NSObject alloc] init]; // 对象1
// object1 持有对象1的强引用
id __strong object2 = [[NSObject alloc] init]; // 对象2
// object2 持有对象2的强引用
id __strong object3 = nil;
// object3 不持有任何对象

object1 = object2;
// object1 持有由 object2 赋值的对象2的强引用
// 由于 object1 被赋值,持有的对对象1的强引用失效;对象1的所有者不存在,所以废弃对象1。
object3 = object1;
// object3 持有由 object1 赋值的对象2的强引用。
// 此时,持有对象2的强引用的变量有 object1,object2 和 object3。
object1 = nil;
object2 = nil;
object3 = nil;
// nil被赋予 object1,object2 和 object3,对对象2的强引用失效。
// 对象2的所有者不存在,所以废弃对象2。

__weak 修饰符

带有 __strong 修饰符的成员变量在持有对象时,很容易发生循环引用,从而容易引起内存泄漏(内存泄漏就是应当废弃的对象在超出其生存周期后继续存在)。

使用 __weak 修饰符可以避免循环引用,__weak 修饰符和 __strong 相反,提供弱引用。弱引用不持有对象实例。

以下是带有 __strong 修饰符的成员变量在持有对象时发生循环引用的例子

@interface Test : NSObject
{
    id __strong obj_;
}
- (void)setObject:(id __strong) obj;
@end

@implementation Test
- (id)init
{
    self = [super init];
    return self;
}

- (void)setObject:(id __strong)obj
{
    obj_ = obj;
}
@end

以下为循环引用

{
    id test1 = [[Test alloc] init];//对象1
    // test1 持有 Test 对象1的强引用。
    id test2 = [[Test alloc] init];//对象2
    // test2 持有 Test 对象2的强引用。
    
    [test1 setObject:test2];
    // Test 对象1的 obj_ 成员变量持有 Test 对象2的强引用。
    // 持有 Test 对象2的强引用的变量为 Test 对象1的 obj_ 和 test2。
    [test2 setObject:test1];
    // Test 对象2的 obj_ 成员变量持有 Test 对象A的强引用。
    // 持有 Test 对象1的强引用的变量为 Test 对象2的 obj_ 和 test1。
}
    // test1 变量超出其作用域,强引用失效,自动释放 Test 对象1。
    // test2 变量超出其作用域,强引用失效,自动释放 Test 对象2。
    // 此时持有 Test 对象1的强引用的变量为 Test 对象2的obj_。
    // 此时持有 Test 对象2的强引用的变量为 Test 对象1的obj_。
    // 发生内存泄漏。

类成员变量的循环引用

对自身的强引用

id test [[Test alloc] init];
[test setObject:test];

解决循环引用的方案
由于弱引用不持有对象,所以在超出其变量作用域时,对象即被释放。
所以将可能发生循环引用的类成员变量改为带 __weak 修饰符的成员变量,可以避免循环引用现象。

@interface Test : NSObject
{
    id __weak obj_;
}
- (void)setObject:(id __strong)obj;
@end

使用 __weak 修饰符注意事项

id __weak obj = [[NSObject alloc] init];

编译运行会出现以下警告


上面的代码将自己生成并持有的对象赋值给带有 __weak 修饰符的变量 obj,即变量 obj 持有对持有对象的弱引用。为了不以自己持有的姿态来保存自己生成并持有的对象,生成的对象会立即被释放。这是编译器的警告,将对象赋值给带 __strong 修饰符的变量之后再赋值给带 __weak 修饰符的变量,就不会出现警告。

{
    id __strong object1 = [[NSObject alloc] init];
    id __weak object1 = object1;
}

__unsafe_unretained 修饰符

__unsafe_unretained 修饰符是不安全的所有权修饰符。
带有 __unsafe_unretained 修饰符的变量不属于编译器的内存管理对象。

id __unsafe_unretained object = [[NSObject alloc] init];

编译运行会出现以下警告


__unsafe_unretained 修饰符的变量和带 __weak 变量一样,为了不以自己持有的姿态来保存自己生成并持有的对象,生成的对象会立即被释放。

使用 __unsafe_unretained 修饰符注意事项
使用 __unsafe_unretained 修饰符时,赋值给带 __strong 的变量时要确保被赋值的对象确实存在。

__autoreleasing 修饰符

ARC有效时,需要通过将对象赋值给带 __autoreleasing 修饰符的变量来替代调用 autorelease 方法。对象赋值给带有 __autoreleasing 修饰符的变量等价于在 ARC 无效时调用对象的 autorelease 方法,即对象被注册到 autoreleasepool

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[obj autorelease];
[pool drain]

等价于

@autoreleasepool{
    id __autoreleasing obj2;
    obj2 = obj;
}

作为 allocnewcopymutableCopy 方返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成并持有的对象。所以使用带有 __autoreleasing 修饰符的变量作为对象取得参数,与除 allocnewcopymutableCopy 外其他方法的返回值取得对象完全一致,都会注册到 autoreleasepool,并取得非自己生成并持有的对象。(编译器会检查方法名是否以 allocnewcopymutableCopy 开始,如果不是就自动将返回值得对象注册到 autoreleasepool

@autoreleasepool{
    // 取得非自己生成并持有的对象
    id __strong obj = [NSMutableArray array];
    // 变量 obj 为强引用,自己持有对象。
    // 该对象由编译器判断其方法名后自动注册到 autoreleasepool
}
   // 变量 obj 超出其作用域,强引用失效,自动释放对象
   // @autoreleasepool 块的结束,注册到 autoreleasepool 中的所有对象被自动释放

访问带有 __weak 修饰符的变量必须访问注册到 autoreleasepool 的对象,因为 __weak 修饰符只持有对象的弱引用,而在访问对象的工程中,对象有可能被废弃。如果要把访问的变量注册到 autoreleasepool 中,那么在 @autoreleasepool 块结束前均能保证该对象存在。

id __weak obj2 = obj1;

等价于

id __weak obj2 = obj1;
id __autoreleasing temp = obj2;

最后

本人为iOS开发新手一枚,写的不好的或写错的地方,希望各位大侠能帮忙指正。
各位大侠,如果觉得对自己有点用的,欢迎点个赞,也欢迎大家关注我( Github / 简书 / 微博 / Instagram / 知乎)
谢谢观看此文。

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

推荐阅读更多精彩内容