美文网首页
【iOS夯实】内存管理之ARC的基本使用

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

作者: 陈炯 | 来源:发表于2017-05-09 23:49 被阅读40次

什么是 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 / 知乎)
谢谢观看此文。

相关文章

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/xefwtxtx.html