ARC 基础(上)

    IOS5 中最具颠覆性的变化当属自动引用计数(Automatic Reference Counting)的引入,缩写为 ARC。ARC 是新的 LLVM 3.0 编译器具备的特性之一,这项技术完全摒弃了让所有 IOS 开发者由爱生恨的手动内存管理。在你的工程中使用 ARC非常简单。你还像往常一样编程,只是不再调用 retain,release 和 autorelease 了。这基本上就是 ARC 的全部。如果开启了自动引用计数,编译器就会在你程序中的恰当位置,插入 retain,release 和 autorelease。你不必再为这个操心,因为编译器会替你搞定。

 

    ARC 基本上是 LLVM 3.0 编译器而非 iOS5 的新特征,所以你也可以将它用在 iOS4.0 及以上版本上。ARC 唯一需要 iOS5 的部分是弱指针。这意味着如果你想在 iOS4 上 部署 ARC,你就不能使用弱属性或者__weak 变量。 

    认识到 ARC 是 objective-c 编译器的一个特性是很重要的,因此与 ARC 相关的一切都发生在构建你的程序时。ARC 不是一个运行时特性(有一小部分例外,就是弱指针系统), 它也不是你从其他语言了解的垃圾回收。ARC 所作的只是在编译代码时向其中插入 retain 和 release,就在你会自己添加他们的地 方 -- 或者至少是你应该添加他们的地方。这就使 ARC 和手动管理代码一样快速,有时候 甚至更快一些,因为它私下可以执行某些优化。 

    诸如firstName 和 textField 这类指针称为"强"(""strong")指针,因为他们保持了对象的存在。默认情况下,成员变量和局部变量是强指针。 同样也存在"弱"指针("weak" pointer)。弱指针变量仍然何以指向对象,但是不再成为所有者。

1 __weak NSString *weakName = self.textField.text;

    weakName 指针变量指向和 textField.text 属性相同的 string 对象,但不再是所有者。如果文本框的内容改变,该 string 对象不再有所有者,所以被释放了。当这发生(对象被释放)时,weakNae 的值被自动置为 nil。即所谓"归零"("zeroing")弱指针。 

    注意这是特别便利的特性,因为它防止了弱指针指向已被释放的内存。这类事情过去曾导致了大量的 BUG -- 你可能听说过"野指针" 或者 "僵尸" -- 但是感谢这些归零弱指针,那些事不再会发生!你大概不会频繁使用弱指针。他们在两个对象是父子关系时最有用。父母会对孩子拥有 强指针 --- 因此"拥有"孩子 -- 但是为了防止所有权循环,孩子仅对父母拥有弱指针。 

1 __weak NSString *str = [[NSString alloc] initWithFormat:...]; 
2 NSLog(@"%@", str); // will output "(null)"

    string 对象没有所有者(因为 str 是弱指针),所以对象会在创建后立刻被释放。Xcode 在你做这件事的时候会给出一个警告因为这可能并非是你所希望发生的事情("Warning: assigning retained object to weak variable; object will be released after assignment")。 

    自动引用计数也有一些限制。作为起步,ARC 只对 objective-c 有效。如果你的程序使用 core fundation 或者 malloc()和 free(),那么你仍然对其内存管理负有责任。 

    AFHTTPRequestOperation.h/.m: AFNetworking 库的一部分,它使对 web service 的请 求更易于执行。 https://github.com/gowalla/AFNetworking 

    SVProgresHUD.h/.m/.bundle 一个会在搜索时显示于屏幕上进度指示器。你以前可能 没见过,bundle 文件。这是一个特殊类型的文件夹,它包含了 SVProgressHUD 要用 到的图片文件。

要查看这些文件,可以右键点击.bundle 文件,选择“查看包内容” (Show Package Contents)菜单选项。 https://github.com/samvermette/SVProgressHUD 

 1.xcode 有一个自动转化工具,能够转换你的代码。

 2.你可以手工转化这些文件。

 3.你可以对你不希望转化的源文件禁用 ARC。 

    ARC 是新的 LLVM 3.0 编译器的一个特性。你的现有工程很可能使用的是早先的 gcc 4.2 或者 LLVM-GCC 编译器,所以你应该首先将工程切换到新编译器,看看在非 ARC 模式下是否存在错误。

转到 Project Settings 屏幕,选中 Artists target,在 Build Setting 的搜 索框中输入"compiler"。修改"Compiler for C/C++/Objective-C"选项,选择 Apple LLVM compiler 3.0+。

    在 warning 抬头部分,还要将"Other Warning Flags"设置为-wall。编译器现在会检查所有可能导致问题的状况。默认情况下,大多数这类警告都被关掉了,但是我发现总是将他们当 作致命错误是很有用的。

   在 Build Options 抬头下的将 Run Static Analyzer 设置为YES.

    Build Settings 屏幕中,切换到"All"来查看所有可用的设置项(和 Basic 不一样, Basic 仅仅显示常用设置项)。搜索"automatic"并设置"Objective-C Automatic Reference Counting"选项为 YES

    xcode 有一个自动转化工具,能够转换你的代码,选择"Edit\Refactor\Convert to Objective-C ARC"。 

    一般说来,iOS 中基于 C 的 API 会使用 core fundation 对象(CF 就代表它),而基于 Objective-C 的 API 使用由 NSObject 扩展而来的真正的对象。有时候你需要在这二者之间 进行转换,而这是免费桥接技术所允许的。 

    Apple 引入了一组关键字:__bridge, __bridge_transfer __bridge_retained 。

    @property (strong, nonatomic)  strong 关键字代表你的意图。它告诉 ARC 属性背后被同步的 ivar 拥有对对象的强引用。也就是说,window 属性包含了一个指向 UIWindow 对象的指针,同时它也成为了 UIWindow 对象的所有者。只要 window 属性还保存着 UIWindow 对象的值,它(UIWindow 对象)就仍然存在于内存中。 

 

    在 ARC 以前,如果属性被声名为 retain,下面的代码会导致内存泄漏: 

1 self.someProperty = [[SomeClass alloc] init];

    init 方法返回一个被保留的对象,而将其赋给这个属性会再次保留它。这就是你为什么 要用 autoreleae,为了平衡 init 方法中的 retain。但是用 ARC 的话,上面的代码是没问题的。

编译器足够聪明,它知道这里不应该做两次 retain。 

    我喜爱 ARC 的特性之一是,在大多数情况下,你都不必写 dealloc 方法。 有时dealloc 方法还是必要的。大多数情况下,你可以忘掉 dealloc,编译器会帮你 搞定。但是有时,你还是需要手动释放资源。这个类就是这种情况。

当 SoundEffect 对象被 析构时,我们仍然需要调用 AudioServicesDisposeSystemSoundID()来清理声音对象,dealloc 就是调用它的绝佳位置。 

    类扩展很酷的地方在于,它允许你为类添加私有属性和方法名。如果你不希望将某些属 性或方法暴露在公共的@interface 中的话,你可以用类扩展。 

    我们将 Build Settings 内的 Objective-C Automatic Reference Counting 设置为 YES 时,已经在程序范围内启用了 ARC。但是通过使用-fno-objc-arc 标志,你可以让编译器对特定的文件不使用 ARC。

xcode 会以关闭 ARC 的方式对这些文件进行编译。 

    "Cast ... requires a bridged cast"

    这是我们之前碰到过的。如果编译器不能自己确定如何转换,他会期待你插入一个 __bridge 修饰符。另外还有两种 bridge 类型,__bridge_trasfer 和__bridge_retained,你要使用 哪一个完全取决于你想做什么。 

    "ARC forbids Objective-C objects in structs or unions"

    ARC 的限制之一是,你不再能够将 Objective-C 对象放到 C 结构中了。 

    一般情况下,实例变量应该是 类的内部实现的一部分,而不是你想暴露在公共接口中的东西。对类的使用者来说,类的成 员变量是什么无关紧要。

从数据隐藏的角度来看,我们应该将这些实现细节放到类的 @implementation 部分。 

 1 #import <UIKit/UIKit.h>
 2 @interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate>
 3 @property (nonatomic, retain) IBOutlet UITableView *tableView;
 4 @property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
 5 @end 
 6 
 7 @implementation MainViewController {
 8   NSOperationQueue *queue;
 9   NSMutableString *currentStringValue;
10 } 

这项技术让你的.h 文件更加简洁,而将成员变量放到他们真正属于的地方。 

保留 dealloc 方法的唯一情况是,当你需要释放某些不在 ARC 保护伞之下的资源时。比如说对 Core Fundation 对象调用 CFRelease(),对使用 malloc()分配的内存调用 free(),反注 册通知,停止记时器,等等。

作为最佳实践,如果你定义了一个属性,你应该总是使用属性。唯一需要访问属性背后的成员变量的地方是在 init 中,或者当你提供自定义的 getter 和 setter 方法时。这就是为什么同步声明经常修改成员变量的原因: 

@synthesize propertyName = _propertyName

这种结构将防止你意在使用"self.propertyName"的时候,输入"propertyName"从而误用了 其背后的成员变量。 

 

@property (nonatomic, weak) IBOutlet UITableView *tableView;

@property (nonatomic, weak) IBOutlet UISearchBar *searchBar; 

对于所有的 outlet 属性推荐使用弱关系。这些视图对象已经是视图控制器层次结构的一部分了,不需要在别的地方 retain 它。

将 outlet 声明为弱引用的最大好处是,你不需要再花时间编写你的 viewDidUnload 方法了。 

因为 tableView 和 searchBar 属性是弱引用,他们在所指向的对象被销毁后自动被 设为 nil。这就是我们所谓的“归零”弱指针。 

• strong. 是 retain 的同义词。一个强属性会成为所指向对象的所有者。

• weak. 这个属性代表一个弱指针。当所指向的对象被释放时,他会自动被设为 nil。记 住,对于 outlet 使用它。

• unsafe_unretained.这是原来的"assign"的同义词。它只在特殊情况下以及你想将目标设 为 iOS4 时使用。后面会讲到它。

• copy. 这还是和以前一样。这将制作对象的一份拷贝,并创建强关系。
• assign. 你能再为对象使用它了,但你还是可以用于基础类型如 BOOL, int 和 float。 

 

CFSTR()宏用一个指定的字符串创建一个 CFStringRef 对象。这个字符串常量是一个标 准的 C 字符串,所以不需要以@打头。不需要将 NSString 对象转换为 CFStringRef,我们能直接得到一个 CFStringRef 对象。 

当你在两个世界之间移动对象时,桥接转换(bridged cast)是必要的。一边是 objective-c 的世界,另一边是 Core Foundation。 

在所有情况下,NSString 和 CFStringRef 可以当作一个东西对待。你可以接受一个 NSString 对象,把它当作一个 CFStringRef 对象,或者将 CFStringRef 对象用作 NSString。这 就是免费桥接背后的思想。之前这只需要做一个简单的转换: 

 1 CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name]; 
 2 
 3 // ....
 4 
 5 CFRelease(s1); 
 6 
 7 
 8 CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, bytes, kCFStringEncodingMacRoman); 
9 NSString *s3 = (NSString *)s2; 10 // release the object when you're done 11 [s3 release];

现在我们有了 ARC,编译器需要知道谁负责释放那些转换的对象。如果你将 NSObject 作为 Core Fundation 对象,那么 ARC 不会负责释放它。但你确实需要告诉 ARC 你的意图, 编译器不能自己来推断。同样的,如果你创建了一个 Core Fundation 对象但将其转换为了 NSObject 对象,你就需要告诉 ARC 得到它的所有权,并及时释放它。这就是桥接转换要做 的。 

 1 - (NSString *)escape:(NSString *)text
 2 {
 3   return (NSString *)CFURLCreateStringByAddingPercentEscapes(
 4     NULL,
 5     (__bridge CFStringRef)text,
 6     NULL,
 7     CFSTR("!*'();:@&=+$,/?%#[]"),
 8       CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
 9 } 

• __bridge 当你想将一种类型临时当作另一种类型使用,而不转移所有权时,需要使用__bridge, ARC 仍旧管理着它。

• __bridge_transfer: 给予 ARC 所有权

1 NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name];
2 CFStringRef s2 = (__bridge_retained CFStringRef)s1;
3 // do something with s2 // . . .
4 CFRelease(s2); 

• __bridge_retained解除 ARC 的所有权 

    不是所有的 Objective-c 和 Core Fundation 对象可以免费桥接的。比如,CGImage 和 UIImage 就不能彼此转换,CGColor 和 UIColor 也不行。

我们已经添加了一个委托协议,它带有一个方法,同时添加了一个遵守该协议的属性。 注意该属性被声明为"weak"。保持委托指针为弱指针是必须的,这样可以防止所有权循环 (ownership cycles)。

你应该熟悉保留循环(retain cycle)的概念,两个对象互相保留,这会导致他们都得不到 释放。这是内存泄漏的常见形式。在实现垃圾回收(GC)处理内存管理的系统中,垃圾搜 集器可以识别这种循环,并会释放他们。但是 ARC 不是垃圾回收,仍然需要你自己处理所 有权循环。弱指针是打断这类循环的重要工具。 

    除了 strong 和 weak,还有一个新的修饰符,unsafe_unretained。一般情况下你不会用到 它。编译器不会为被声明为 unsafe_unretained 的变量或属性自动添加 retain 或 release。

这个新修饰符采用"unsafe"来组成它的名字,这是因为它可以指向一个不再存在的对 象。如果你试着使用那种指针,你的程序极有可能崩溃。这是需要用 NSZombieEnabled 调 试工具来找出的问题。技术上讲,如果你不使用任何 unsafe_unretained 属性或变量,你绝不 会再向已经被释放的对象发送消息。

大多数时间里,你都希望用 strong,有时也用 weak,几乎不会用 unsafe_unretained。 unsafe_unretained 仍然还存在的原因,是为了兼容 iOS4,在 iOS4 中弱指针系统不可用,另 外的原因是为了一些其他的技巧。 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2013-02-13 13:16  diablo大王  阅读(3889)  评论(0编辑  收藏  举报