iOS:新特性三(iOS5.0/xcode4.2)--ARC

  • 适用:ios5以后;
  • 原理:类似MRC。
  • 关键字:strong类似retain;weak类似assign;unsafe_unretained和weak类似,指针指向的地址一旦被释放,weak指针将被赋值为 nil,而unsafe_unretained指针为野指针;__autoreleasing类似 autorelease,建议用在传引用参数当中。详见官方文档说明。
  • 规则:
    1. retain, release, autorelease, dealloc由编译器自动插入,不能在代码中调用
    2. dealloc虽然可以被重载,但是不能调用[super dealloc]
    3. 不能使用NSAllocateObject, NSDeallocateObject
    4. 不能在C结构体中使用对象指针
    5. id与void *间的如果cast时需要用特定的方法(__bridge关键字)
    6. 不能使用NSAutoReleasePool、而需要@autoreleasepool块
    7. 不能使用“new”开始的属性名称 (如果使用会有下面的编译错误”Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects”)

说明:(以下个人理解)

  1. strong,weak, unsafe_unretained往往都是用来声明属性的,如果想声明临时变量就得用__strong, __weak, __unsafe_unretained, __autoreleasing, 其用法类似;
  2. ARC中__strong和__weak等这些Quilifiers,实质上还是retain/release那套东西,只不过被编译器翻译成合适的retain/release操作
  3. 对于__autoreasing,将编译如下,其他地方没什么用,官方推荐它只用于修饰类型为引用的参数(is used to denote arguments that are passed by reference (id *) and are autoreleased on return)。
    NSNumber * __autorelease number = [[NSNumber alloc] initWithInt:13];
    将会被编译成
    NSNumber * number = [[[NSNumber alloc] initWithInt:13] autorelease];
  4. 当__autoreasing用作修饰引用参数的时候,The mismatch between the local variable declaration (__strong) and the parameter (__autoreleasing) causes the compiler to create the temporary variable.具体如下:
  5. 综合可理解为[[object alloc]init]等各种创建方式retainCount都给置成零,然后如果是strong的对象指向它,retainCount加1,指针断开的时候retainCount减1;weak等对象指向它,retainCount不变。 对于下面的例子,因为参数是引用类型,传过来是一个指向对象的指针,所以上面那套机制不适用了,所以用__autoreleasing修饰。相当指针所指对象值为[[…init]autorelease],之后外面local variable赋值的时候就一切正常了。
NSError * __strong e;
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;{  
    ....  
    *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];  
}
//The compiler therefore rewrites the code
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
    // Report the error. }
  1. 之前MRC中对于方法中返回创建的对象都是autorease,因为必须要遵守MRC里的规则,也体现了封装性;这里也是一样,因为你不知道传入的参数到底是__strong类型的还是__weak类型的,为了保证什么情况都能用/正常(如果参数传入的是weak类型的话,在方法结束的时候,对象(局部变量)可能释放了),所以借用一个临时的autorease变量,也就是__autorease修饰词作用。

备注:

  1. 关于ARC在IBOutlet中使用,原则: Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib file (or, in iOS, a storyboard scene) which should be strong。(主要看想不想拥有它,前者一般被他父视图拥有非控制器,后者被控制器拥有)。
  2. ARC equivalent of autorelease?:
+(MyCustomClass*) myCustomClass
{  return [[[MyCustomClass alloc] init] autorelease];}
    This code guarantees the returning object is autoreleased. What's the equivalent with ARC?-->simply use
+(MyCustomClass*) myCustomClass
{   return [[MyCustomClass alloc] init];}

之前在MRC的时候,需要autorealease是因为和init对应,遵守规则。所以进入了一个误区,认为方法return之后,方法里的局部变量都会被释放掉,包括return的类。其实方法结束后,方法里的局部变量会被释放掉,但是return的类不会。所以ARC下,方法不需要做什么修改,方法返回的类遵循着ARC规则。而MRC中添加autorelease而不是release是防止类在方法return之前将对象释放掉。
3. 因为ARC中成员/局部变量默认的指针类型就是strong;
4. ARC相比MRC对一些代码带来了影响:例如,例1没意义了,例2变正常了

NSLog(@"%@",str); //输出是"(null)" id obj = [array objectAtIndex:0];  
[array removeObjectAtIndex:0]; 
  1. 在MRC的年代使用点方法和property访问成员变量可以简化对象的retain和copy,而在ARC时代,这就显得比较多余了。property只在将需要的数据在.h中暴露给其他类时才需要,而在本类中,只需要用实例变量就可以,使用property和点方法来调用setter和getter比较多余。
  2. ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是在代码编译时为你自动在合适的位置插入release或autorelease,就如同之前MRC时你所做的那样。因此,至少在效率上ARC机制是不会比MRC弱的,而因为可以在最合适的地方完成引用计数的维护,以及部分优化,使用ARC甚至能比MRC取得更高的运行效率。

使用

  • xcode4.2 中缺省Objective-C Automatic Reference Count就是 ON 的状态,新建项目就是arc项目;
  • 将非ARC项目转换成arc:
    1. 将项目编译环境ARC设置ON
    2. 使用工具Xcode -> Edit/Convert -> Refactor -> Convert to Objective-C ARC(转换工具会自动将代码里面的 retain、release、autorelease 等操作去掉,属性中的 retain、copy、assign 转为为对应的关键字)
    3. 解决 ARC 不允许转换的问题:转换前可能会弹出提示“Cannot Convert to Objective-C ARC”,按照提示将问题解决好就行;
    4. 再次选择“Convert to Objective-C ARC…”,你就能看到一个界面,会列出了将会为了转换的代码的对照列表。默认所有出现在列表里面的文件都是选中的,你可以勾掉不要转换的文件,确认完成;
    5. 备注:(#define …. [xx release]….转换工具是不会自动处理里面的 release 的,需要手动将[xx release];去掉) + (此外在 CF 对象与 NS 对象之间转换的需要加上关键字__bridge,这个也需要手动来来修改);
  • 某些文件支持arc否:在Build Phases中设置:-fobjc-ARC 或者-fno-objc-ARC;
  • 对于第三方包的问题,第三方框架的问题:大部分第三方框架都是用一些宏来让代码可以同时适应ARC和非ARC的(用#if __has_feature(objc_ARC)判断)。如果代码量不大,可以考虑自己进行改写。你可以按上面的步骤将第三方框架自己手动改成ARC。如果是大型框架的话,可以采取标记此框架保留非ARC的环境不变,继续使用。

代码适配arc

.#ifndef PX_STRONG
.#if __has_feature(objc_arc)
.#define PX_STRONG strong
.#else
.#define PX_STRONG retain
.#endif
.#endif

.#ifndef PX_WEAK
.#if __has_feature(objc_arc_weak)
.#define PX_WEAK weak
.#elif __has_feature(objc_arc)
.#define PX_WEAK unsafe_unretained
.#else
.#define PX_WEAK assign
.#endif
.#endif

.#if __has_feature(objc_arc)
.#define PX_AUTORELEASE(expression) expression
.#define PX_RELEASE(expression) expression
.#define PX_RETAIN(expression) expression
.#else
.#define PX_AUTORELEASE(expression) [expression autorelease]
.#define PX_RELEASE(expression) [expression release]
.#define PX_RETAIN(expression) [expression retain]
.#endif

例如dealloc方法:

- (void)dealloc {
 ......
[self removeObserver:self forKeyPath:keyPath];//如果有的话
 #if !__has_feature(objc_arc) 
    [array release]; 
    [super dealloc];
#endif
}

桥接__bridge

//我们先来看一下ARC无效的时候,我们写id类型转void*类型的写法
id obj = [[NSObject alloc] init];
void *p = obj;
//反过来,当把void*对象变回id类型时,只是简单地如下来写:
id obj = p;
[obj release];
//但是上面的代码在ARC有效时,就有了下面的错误:
error: implicit conversion of an Objective-C pointer
        to ’void *’ is disallowed with ARC
    error: implicit conversion of a non-Objective-C pointer
        type ’void *’ to ’id’ is disallowed with ARC

为了解决这一问题,我们使用 __bridge 关键字来实现id类型与void*类型的相互转换。看下面的例子。

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

将Objective-C的对象类型用 __bridge 转换为 void* 类型和使用 __unsafe_unretained 关键字修饰的变量是一样的。被代入对象的所有者需要明确对象生命周期的管理,不要出现异常访问的问题。
除过 __bridge 以外,还有两个 __bridge 相关的类型转换关键字:__bridge_retained 和 __bridge_transfer,接下来,我们将看看这两个关键字的区别:

id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
// 从名字上我们应该能理解其意义:类型被转换时,其对象的所有权也将被变换后变量所持有。
//如果不是ARC代码,类似下面的实现:
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
//可以用一个实际的例子验证,对象所有权是否被持有。
void *p = 0;
{
    id obj = [[NSObject alloc] init];
    p = (__bridge_retained void *)obj;
}
NSLog(@"class=%@", [(__bridge id)p class]);
//出了大括号的范围后,p 仍然指向一个有效的实体。说明他拥有该对象的所有权,该对象没有因为出其定义范围而被销毁。

__bridge_transfer: 相反,当想把本来拥有对象所有权的变量,在类型转换后,让其释放原先所有权的时候,需要使用 __bridge_transfer 关键字。

//如果ARC无效的时候,我们可能需要写下面的代码。
// p 变量原先持有对象的所有权
id obj = (id)p;
[obj retain];
[(id)p release];
//那么ARC有效后,我们可以用下面的代码来替换:
// p 变量原先持有对象的所有权
id obj = (__bridge_transfer id)p;

可以看出来,__bridge_retained 是编译器替我们做了 retain 操作,而 __bridge_transfer 是替我们做了 release。

Toll-Free bridged

在iOS世界,主要有两种对象:Objective-C 对象和 Core Foundation 对象0。Core Foundation 对象主要是有C语言实现的 Core Foundation Framework 的对象,其中也有对象引用计数的概念,只是不是 Cocoa Framework::Foundation Framework 的 retain/release,而是自身的 CFRetain/CFRelease 接口。

这两种对象间可以互相转换和操作,不使用ARC的时候,单纯的用C原因的类型转换,不需要消耗CPU的资源,所以叫做 Toll-Free bridged。比如 NSArray和CFArrayRef, NSString和CFStringRef,他们虽然属于不同的 Framework,但是具有相同的对象结构,所以可以用标准C的类型转换。

//比如不使用ARC时,我们用下面的代码:
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (CFStringRef)string;
//同样,Core Foundation类型向Objective-C类型转换时,也是简单地用标准C的类型转换即可。
//但是在ARC有效的情况下,将出现类似下面的编译错误:
Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ (aka ‘const struct __CFString *’) requires a bridged cast
    Use __bridge to convert directly (no change in ownership)
    Use __bridge_retained to make an ARC object available as a +1 ‘CFStringRef’ (aka ‘const struct __CFString *’)
//错误中已经提示了我们需要怎样做:用 __bridge 或者 __bridge_retained 来转型,其差别就是变更对象的所有权。

正因为Objective-C是ARC管理的对象,而Core Foundation不是ARC管理的对象,所以才要特意这样转换,这与id类型向void*转换是一个概念。也就是说,当这两种类型(有ARC管理,没有ARC管理)在转换时,需要告诉编译器怎样处理对象的所有权。

上面的例子,使用 __bridge/__bridge_retained 后的代码如下:

// __bridge
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (__bridge CFStringRef)string;
//只是单纯地执行了类型转换,没有进行所有权的转移,也就是说,当string对象被释放的时候,cfString也不能被使用了。
//__bridge_retained
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (__bridge_retained CFStringRef)string;
...
CFRelease(cfString);
 // 由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release
//使用 __bridge_retained 可以通过转换目标处(cfString)的 retain 处理,来使所有权转移。即使 string 变量被释放,cfString 还是可以使用具体的对象。只是有一点,由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release。

实际上,Core Foundation 内部,为了实现Core Foundation对象类型与Objective-C对象类型的相互转换,提供了下面的函数。

CFTypeRef  CFBridgingRetain(id  X)  {
    return  (__bridge_retained  CFTypeRef)X;
}
id  CFBridgingRelease(CFTypeRef  X)  {
    return  (__bridge_transfer  id)X;
}
//所以,可以用 CFBridgingRetain 替代 __bridge_retained 关键字:
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = CFBridgingRetain(string);
...
CFRelease(cfString); 
// 由于Core Foundation不在ARC管理范围内,所以需要主动release。
//__bridge_transfer
所有权被转移的同时,被转换变量将失去对象的所有权。当Core Foundation对象类型向Objective-C对象类型转换的时候,会经常用到 
CFStringRef cfString = CFStringCreate...();
NSString *string = (__bridge_transfer NSString *)cfString;
// CFRelease(cfString); 
//因为已经用 __bridge_transfer 转移了对象的所有权,所以不需要调用 release
//同样,我们可以使用 CFBridgingRelease() 来代替 __bridge_transfer 关键字。
CFStringRef cfString = CFStringCreate...();
NSString *string = CFBridgingRelease(cfString);

总结

  1. Core Foundation 对象类型不在 ARC 管理范畴内
  2. Cocoa Framework::Foundation 对象类型(即一般使用到的Objectie-C对象类型)在 ARC 的管理范畴内
  3. 如果不在 ARC 管理范畴内的对象,那么要清楚 release 的责任应该是谁,各种对象的生命周期是怎样的;
  4. 一个从Objc到Core Foundation,另外反之,注意区别:
    1. __bridge transfers a pointer between Objective-C and Core Foundation with no transfer of ownership.
    2. __bridge_retained casts an Objective-C pointer to a Core Foundation pointer and also transfers ownership to you.
      You are responsible for calling CFRelease or a related function to relinquish ownership of the object.
    3. __bridge_transfer moves a non-Objective-C pointer to Objective-C and also transfers ownership to ARC.
      ARC is responsible for relinquishing ownership of the object

参考:

  • (手把手教你ARC)http://onevcat.com/2012/06/arc-hand-by-hand/
  • (arc关键字)http://blog.sina.com.cn/s/blog_801997310101a6hg.html
  • (arc在IBOutlet)http://nijino.cn/blog/2013/06/24/iboutlet-arc/
  • (__bridge)http://blog.csdn.net/sanpintian/article/details/8139878
  • (官方)https://developer.apple.com/library/mac/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

你可能感兴趣的:(ios,arc)