iOS-ARC

2017-08-31  本文已影响5人  doudo

本文的内容包括

一、所有权修饰符
二、ARC的基本规则
三、ARC的实现

ARC中仍然是通过引用计数来管理内存,这个本质没有变。只是,不需要我们手动的写代码去管理内存了,编译器自动帮助我们管理“引用计数”相关的部分。那么ARC是通过什么方式来帮我们管理内存的呢?

ARC是通过编译期运行期两部分来处理的:

  • 编译期,编译器不是通过添加retain/release/autorelease这些方法,而是会直接调用更底层的C语言函数(如objc_retain)。
  • 运行期,ARC也包含运行期组件。比如,某些类方法返回对象前,为其执行了autorelease操作,而大多数情况,我们会对返回的对象保留,比如:_myPerson = [Person personWithName:@"Kobe"];
    那么其实就相当于先执行了一个autorelease,然后又retain了一下。ARC可以在运行期检测到这一多余的操作,也就是autorelease后紧跟retain。那么会它们两个会被改为调用objc_autoreleaseReturnValueobjc_retainAutoreleasedReturnValueobjc_autoreleaseReturnValue会检查后边是否紧接着调用objc_retainAutoreleasedReturnValue,如果是,就不将返回的对象注册到autoreleasepool中而直接传递,省略了autorelasepool注册,实现了最优化。

一、所有权修饰符

1. __strong修饰符

如它的名字一样,__strong表示对对象的强引用。持有强引用的变量在超出其作用域的时候被废弃,随着强引用的失效,引用的对象会随之释放。

知识点:

  1. __strong修饰符是id类型和对象类型默认的所有权修饰符,所以一般不写__strong。
  2. __strong和__weak和 __autoreleasing修饰符一样,可以将附有这些修饰符的自动变量初始化为nil。

2. __weak(iOS5以上才有,之前用__unsafe_unretained)

如它的名字一样,__weak修饰符表示对对象的弱引用。不持有对象。

知识点:

  1. 可以避免循环引用。
  2. 变量指向的对象被销毁了,变量也会自动置空为nil。

3. __unsafe_unretained

如它的名字一样,它是不安全的所有权修饰符。

知识点:

  1. 和__weak一样表示弱引用,不持有对象,但不会置nil,这也正是不安全的原因。
id __unsafe_unretained obj1 = nil;
    {
        id obj0 = [[NSObject alloc] init];
        obj1 = obj0;
        NSLog(@"A:%@",obj1);
    }
    NSLog(@"B:%@",obj1);

输出A和B虽然是一样的地址,但是此时B处obj1已经是野指针了。

4. __autoreleasing

通过给对象附加__autoreleasing修饰符 来替代调用autorelease方法,把对象注册到autoreleasepool中。

具体使用情况,在这里

二、规则

1. 不能使用retain/release/retainCount/autorelease

2. 用@autoreleasepool{}代替NSAutoreleasePool对象

3. 不要显式的调用dealloc

多数情况下在dealloc中删除已注册的代理或观察者。不用书写[super dealloc],因为ARC已经自动处理了。

4. 必须遵守内存管理的方法命名规则

  1. alloc/new/copy/mutableCopy,以上述名称开始的方法在返回对象时,必须返回给调用方所应当持有的对象。这在ARC下,依然没有变。
  2. ARC下追加了一条命名规则:

5. 对象类型不能作为结构体的成员

因为ARC把内存管理的工作分配给了编译器,所以编译器必须能够知道并管理对象的生命周期。例如C语言的自动变量(局部变量)可以使用该变量的作用域来管理。但是对于结构体成员来说,是无法实现的。

6. 显式转换id和void*

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;

但是,__bridge安全性与__unsafe_unretained类似,甚至更低。很容易造成野指针导致崩溃。
__bridge还有另外两种转换,__bridge_retained和__bridge_transfer

  1. Core Foundation 对象类型不在 ARC 管理范畴内,需要自己管理.
  2. __bridge只做类型转换,但是不修改对象(内存)管理权,原来是ARC管理的还用ARC,原来MRC管理的继续用MRC
  3. __bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease或者相关方法来释放对象;
  4. __bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。

5. 不能使用NSAllocateObject/NSDeallocObject

6. 不能使用区域(NSZone)

三、ARC的实现

1. __strong实现

赋值给附有__strong修饰符的变量在实际的程序中是怎样运行的呢?

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

编译器在超出作用域时自动插入了release。

{
        id __strong obj = [NSMutableArray array];
    }

执行,alloc/new/copy/mutableCopy之外的方法,如array类方法:
像代码这样,返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturnValue,如果其后紧接着调用objc_retainAutoreleasedReturnValue(),那么就不将返回的对象注册的autoreleasepool中而直接传递。通过这两个方法,优化了程序运行。

2.__weak的实现

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

objc_initWeak函数中的weak_register_no_lock()把赋值对象obj的地址作为键值,通过哈希查找找到weak弱引用表中对应的数组,将附有__weak修饰符变量的指针添加到数组中。如果没有找到数组,表示是第一个weak指针,则新建一个数组。

对象在被废弃时dealloc方法中会调用object_dispose函数,该方法内部会通过调用weak_clear_no_lock()

  1. 通过哈希查找从weak表中获取废弃对象地址作为键值的记录是一个数组
  2. 将包含在数组中的所有附有__weak修饰符的变量地址,遍历数组赋值为nil。
  3. 从weak表中删除该记录。
  4. 从weak表中删除该键值。

所以,如果大量使用__weak修饰符的变量,则会消耗相应的cpu资源。良策是只在需要避免循环引用的时候使用__weak。

3. __autoreleasing修饰符的实现

objc_autorelease

4. 引用计数

可以通过_objc_rootRetainCount(obj)来获取对象的引用计数值。但实际上并不能完全信任该函数取得的值。对于已经释放的对象以及不正确的对象地址,有时也返回1。另外,在多线程中使用它,因为存在竞态条件的问题,所以取得的数值也不一定完全可信。当然它在调试中还是比较有用的。

上一篇下一篇

猜你喜欢

热点阅读