这是iOS 5 盛宴中的第12篇教程! 这篇教程是我们的新书 iOS 5 By Tutorials 的一个预览章节。 Matthijs Hollemans 是这个章节的作者 – 也是 iOS Apprentice Series 的作者。 希望你喜欢!
这篇文章发表自 iOS 教程团队成员 Matthijs Hollemans, 一个经验丰富的 iOS开发者和设计师。
这是这个分为两部分的教程在 iOS 5 中使用 ARC 的第二部分。
在这篇教程的第一部分,我们概括了 ARC 是如何工作的和如何用 Xcode 内置的转换工具将项目转换到 ARC 模式。
在这篇教程的第二部分, 我们将介绍手工将文件转换到 ARC, ARC 和 Core Foundation 的关系, 弱引用属性, 还有更多!
我们的转换过程很平稳。 我们仅仅对 SoundEffect.m(插入一个 __bridge) 的语句进行了修改,其他的都交给了转换工具。
然而, LLVM 3.0 编译器在 ARC 模式下对错误的容忍度比以前更低, 所以你可能在预检测中收到更多的错误。 在工具能完成之前,你可能需要更多的修改你自己的代码。
这里是你可能遇到的一些问题和解决他们的方法:
“Cast … requires a bridged cast”
这个我们之前看到过。 如果编译器自己没办法确定如何进行类型转换, 它期待你能加入 __bridge 修饰符。 还有其他两个 bridge 类型, __bridge_transfer 和 _bridge_retained, 具体用那个要取决于你想做什么。 关于 bridge 类型的更多信息可以参看 Toll-Free Bridging 这节。
“Receiver type ‘X’ for instance message is a forward declaration”
如果你有一个类 MyView 继承自 UIView, 你调用了他的方法或属性, 那么你需要 #import 这个类。 这个通常需要先编译一下代码,但也不总是这样。
例如,你在 .h 文件中添加了一个 MyView 类的前置声明
@class MyView; |
然后,在 .m 文件中这样:
[myView setNeedsDisplay]; |
在以前,这样可以正常编译,即使没有添加 #import 语句。 在 ARC 中,你必须添加 import:
#import "MyView.h" |
“Switch case 语句需要被保护的作用域”
如果这样写,你会接收到一个错误:
switch (X) { case Y: NSString *s = ...; break; } |
这样不再允许了。 如果你在 case 语句中定义了指针变量, 你必须用花括号把他们包起来:
switch (X) { case Y: { NSString *s = ...; break; } } |
这样,这个变量的作用域就清楚了, ARC 需要知道它的作用域,这样才能正确的释放这个对象。
“A name is referenced outside the NSAutoreleasePool scope that it was declared in”
你可能会这样创建你自己的自动释放池:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // . . . do calculations . . . NSArray* sortedResults = [[filteredResults sortedArrayUsingSelector:@selector(compare:)] retain]; [pool release]; return [sortedResults autorelease]; |
转换工具需要将它转换成这样的形式:
@autoreleasepool { // . . . do calculations . . . NSArray* sortedResults = [filteredResults sortedArrayUsingSelector:@ selector(compare:)]; } return sortedResults; |
但这是无效的代码。 sortedResults 在 @autoreleasepool 的作用域中定义, 在它的外部不能被访问到。 为了修正这个问题,你需要将变量的定义放到创建 NSAutoreleasePool 的上面:
NSArray* sortedResults; NSAutoreleasePool* pool =[[NSAutoreleasePool alloc] init]; . . . |
现在自动转换工具可以正常的重写你的代码。
“ARC 禁止在 Objective-C 中使用结构体和联合体”
ARC 的一个限制就是你不能在 C 接口体中放入 Objective-C 对象了。 下面的代码无效了:
typedefstruct{ UIImage *selectedImage; UIImage *disabledImage; } ButtonImages; |
推荐的方式是将这个结构体直接转换成 Objective-C 对象。 我们会在后面更详细的讨论这个问题, 我会给你展示其他的一些处理方式。
这里可能还有其他的一些错误, 但都是比较常见的。
注意: 如果你使用自动转换工具超过一次,可能会出现比较奇怪的结果。 如果你不想一次转换完所有的文件,而只是转换一部分, 那么自动转换工具在以后就不会对其他文件进行转换。 我的建议是只运行转换工具一次, 并且不要批量转换你的文件。
我们几乎已经把整个项目都转换到 ARC 了, 除了 MainViewController 和 AFHTTPRequestOperation。 在这节,我将向你展示如何手工转换 MainViewController。 有时候你亲自做这些事是很好的, 这样你能够更加明白真正发生了什么。
如果你看一下 MainViewController.h, 你会发现这个类有两个实例变量:
@interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, NSXMLParserDelegate> { NSOperationQueue *queue; NSMutableString *currentStringValue; } |
当你这样想时, 一个类公开的接口中,不是放实例变量的一个好地方。通常,实例变量是你类内部的一部分,不是你想暴露给公共接口的。 对你这个类的用户来说, 知道它的实例变量不是那么重要。 从数据隐藏的角度来看, 如果我们能将这些实现放到这个类的 @implementation 部分中才是更好。 我很高兴的告诉你,现在在 LLVM 3.0 编译器中(不论你是否使用 ARC),这个是可以实现的。
从 MainViewController.h 中删除实例变量块, 把他们放到 MainViewController.m 中。 这个头文件看起来应该是这样:
#import <UIKit/UIKit.h> @interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, NSXMLParserDelegate> @property (nonatomic, retain) IBOutlet UITableView *tableView; @property (nonatomic, retain) IBOutlet UISearchBar *searchBar; @end |
MainViewController.m 的顶部看起来应该是这样:
@implementation MainViewController { NSOperationQueue *queue; NSMutableString *currentStringValue; } |
构建应用。。。 它能正常工作。 这让 .h 文件更加清晰, 并且把 ivar 放到他们应该放到的地方去。
你可以对 SoundEffect 类进行同样的操作。 直接把实例变量的部分放到 .m 文件中去。 因为我们现在不在 SoundEffect.h 中引用 SystemSoundID 符号了。 你还可以把 #import for AudioServices.h 放到 SoundEffect.m 中。 SoundEffect 不再暴露任何它实现的细节。 非常清晰。
注意: 你还能把实例变量放到类扩展中, 在你通过多个文件实现一个类的时候非常有用。 你可以把扩展放到一个共享的, 私有的有文件中, 这样所有不同的实现文件都可以访问到这些实例变量。
现在是时候在 MainViewController.m 上开启 ARC了。 进入 Build Phases 设置, 从 MainViewController.m 中删除 -fno-objc-arc 编译选项。 在这过程中你可能会遇到一些问题。进行一次新的构建。 你应该会收到一大串错误,但是 Xcode 仍然显示 “Build Succeeded”, 然后,关闭项目再重新打开它。
让我们看看这些错误,然后一个一个的修复他们。 我们从 dealloc 开始:
dealloc 中的每一行都会有一个错误。 我们不再被允许调用 [release] 了, 也不允许调用 [super dealloc]。 因为我们在 dealloc 不再做任何操作了, 所以我们可以直接把这个方法删除掉。
唯一一个留着 dealloc 方法的原因就是, 你需要释放一些不在 ARC 控制下的资源。 例如 Core Foundation 对象中调用 CFRelease(), 对那些通过 malloc() 分配的内存调用 free(), 注销通知,停止 Tiner, 等等。
如果你是一个对象的代理的话,有时必须显式的断开和它的连接,但通常这都是自动的。 大部分情况下,代理都是弱引用(我们马上会介绍到), 当一个即将被释放的对象是其他对象的代理的话, 当这个对象被销毁时,代理指针将会被自动设置为 nil。 弱指针在这之后会被自动清楚。
另外, 在你的 dealloc 方法中, 你仍然可以使用实例变量, 因为他们在这时候还没被释放掉。 在 dealloc 返回之前,都不会被释放。
soundEffect 方法调用了 release, 所以修改很简单:
这个方法实际上是 soundEffect 属性的 getter 方法。 它通过延迟加载技术,只有在音效第一次用到的时候才加载。 我在这里用了手工内存管理中创建对象的通用模式。 首先这个新对象存储到一个局部变量中, 然后给他赋值给属性, 最后释放掉这个局部变量。 这是我处理这类问题的方式,你可能也会这么做:
SoundEffect *theSoundEffect = [[SoundEffect alloc] initWithSoundNamed:@"Sound.caf"]; self.soundEffect = theSoundEffect; [theSoundEffect release]; |
我们可以删除掉调用 release 这行代码, 但是现在,这个单独的局部变量也不再有用了:
SoundEffect *theSoundEffect = [[SoundEffect alloc] initWithSoundNamed:@"Sound.caf"]; self.soundEffect = theSoundEffect; |
所以,我们可以把它简化成一行代码:
self.soundEffect = [[SoundEffect alloc] initWithSoundNamed: @"Sound.caf"]; |
在手工内存管理中,这将导致内存泄露,但是 ARC 中,这种情况是没问题的。
就像你不能再调用 release 一样, 你也不能调用 autorelease:
这个修正很直观, 将下面的代码修改为:
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; |
这样:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; |
下一个报错的方法是 escape, 不过我们暂时跳过它, 这些问题和 toll-free bridging 有关, 我准备用单独的一节来介绍它。
剩下的两个错误也都是关于 release 的, 在 searchBarSearchButtonClicked: 和 parser:didEndElement: 方法中。 你可以直接删除这两行。
如果你看看 MainViewController.m 的上面, 你会看到它用类扩展定义了两个私有属性, searchResults 和 soundEffect。
@interface MainViewController () @property (nonatomic, retain) NSMutableArray *searchResults; @property (nonatomic, retain) SoundEffect *soundEffect; @end |
这个主要是为了让内存管理更简单, 它也是开发者为什么要用属性的原因。 如果你这样:
This is done primarily to make manual memory management easier and it’s a common reason why developers use properties. When you do,
self.searchResults = [NSMutableArray arrayWithCapacity:10]; |
setter 方法会负责 release 老的值(如果有的话), 并且正确的 retain 新的值。 开发者已经通过使用属性来不再过多的考虑什么时候需要 retain 和 release。 但是在 ARC 中, 你完全不需要考虑这些了!
我的观点是,为了简化内存管理而使用属性不再是必须的了。 如果你愿意,你还可以这样做, 但是我认为使用实例变量会更好, 只有在你需要通过公共接口将属性暴露给其他类访问时才需要用到属性。
然后,删除类扩展和 searchResults 和 soundEffect 的 @synthesize 语句。 增加新的实例变量来替换他们:
@implementation MainViewController { NSOperationQueue *queue; NSMutableString *currentStringValue; NSMutableArray *searchResults; SoundEffect *soundEffect; } |
当然,这也意味着我们不能在用 self.searchResults 和 self.soundEffect 了。 按照如下方式修改 viewDidUnload 方法:
- (void)viewDidUnload { [super viewDidUnload]; self.tableView = nil; self.searchBar = nil; soundEffect = nil; } |
我们仍然需要设置 soundEffect 为 nil, 因为我们希望在这里释放 SoundEffect 对象。 dang iPhone 收到低内存警告时, 我们需要尽可能多的释放掉内存, 而 SoundEffect 对象正是一个消耗资源的地方。 因为默认情况下,实例变量创建的是强引用, 设置 soundEffect 为 nil 将删除 SoundEffect 对象的所有者, 这样他就会被立即释放掉。
soundEffect 现在是这样:
- (SoundEffect *)soundEffect { if (soundEffect == nil) // lazy loading { soundEffect = [[SoundEffect alloc] initWithSoundNamed:@"Sound.caf"]; } return soundEffect; } |
这是我们能做的最简化的代码了。 SoundEffect 初始化,并且赋值给 soundEffect 实例变量。 这个变量成为了它的所有者, 这个对象会一直存活, 直到我们设置 soundEffect 为 nil(在 viewDidUnload 方法中),或者 MainViewController 被释放掉。
在这个文件的剩余部分,将所有的 self.searchResults 替换成 searchResults。 当你重新构建应用时, 仅仅有一个关于 escape: 方法的错误。
注意在 searchBarSearchButtonClicked 方法中, 我们仍然需要:
[self.soundEffect play]; |
即使没有 soundEffect 这个属性了, 它还能正常工作。 点语法不仅限于属性, 尽管它是最常用的。 如果用点语法让你觉得不舒服, 你可以改成这样:
[[self soundEffect] play]; |
不要把它改成这样:
[soundEffect play]; |
因为你是用延迟加载的 SoundEffect 对象, 在你调用 soundEffect 方法之前, soundEffect 实例变量都是空的。 因此, 我们实际上有一个 SoundEffect 对象, 你应该通过 self 来访问它。 如果你发现这个模式需要你通过 @property 声明 soundEffect, 那么就去做吧。 不同的方式适合不同的人. :)
最为最佳实践, 如果你将一些东西定义为属性, 那么你应该一直把他们当做属性来用。 唯一一个你需要直接访问属性后面的实例对象的地方是在 init 方法中,还有你需要提供自定义的 getter 和 setter 方法的地方。 在其他地方你应该通过 self.propertyName 的方式来访问属性。 这也是为什么 synthesize 语句经常对 ivar 进行重命名:
@synthesize propertyName = _propertyName; |
这种结构能够防止你在应该用 “self.propertyName” 的地方,意外的通过 “propertyName” 来直接使用后端的实例变量。
说到属性, MainViewController 的 .h 文件中, 也有两个 outlet 属性:
@property (nonatomic, retain) IBOutlet UITableView *tableView; @property (nonatomic, retain) IBOutlet UISearchBar *searchBar; |
retain 关键词在 ARC 中仍然有效, 他们只不过是 strong 的一个代号。 不过,最好还是把属性声明为 strong, 因为这才是正确的术语。 但对这两个特殊的属性来说,我有其他的计划。 我们将他们声明成 weak 的, 来代替 strong:
@property (nonatomic, weak) IBOutlet UITableView *tableView; @property (nonatomic, weak) IBOutlet UISearchBar *searchBar; |
Weak 是所有 outlet 属性推荐的声明关系。 这个视图已经是视图控制器视图层级中的一部分了, 不需要在其他地方 retain 了。 声明 weak 的好处是, 省去了你自己写 viewDidUnload 方法的时间。
现在,我们的 viewDidUnload 是这样:
- (void)viewDidUnload { [super viewDidUnload]; self.tableView = nil; self.searchBar = nil; soundEffect = nil; } |
现在你可以把它简化成这样:
- (void)viewDidUnload { [super viewDidUnload]; soundEffect = nil; } |
这是没问题, 因为 tableView 和 searchBar 属性是 weak 的, 当他们所指向的对象被销毁时,他们会自动被设置成 nil。 这也是我们为什么把它叫做 “归零” 弱引用指针。
当 iPhone 收到低内存警告的时候, 视图控制器的主视图会被释放掉, 这样也会释放掉它所有的子视图。 这是 UITableView 和 UISearchBar 对象就不存在了, 并且”归零”弱引用指针系统会自动将 self.table 和 self.searchBar 设置为 nil。 我们不需要在 viewDidUnload 做这些事情了。 事实上, 在 viewDidUnload 被调用时, 这些属性就已经是 nil 了。 我马上会演示这个, 但是我们需要为应用添加第二个界面。
这并不意味着你可以完全地把 viewDidUnload 忘掉。 记住, 只要你保持这一个对象的指针, 它就一直存活。 如果你不想在保持一个对象, 你需要把它的指针设置为 nil。 这正是我们对 soundEffect 做的。 我们不想在这时候删除 searchResults 数组, 但如果我们想的话, 那就可以在这里把它设置为 nil。 任何你不再需要的数据,但它又不是弱引用 outlet 属性,那么你仍然需要在 viewDidUnload 中将他们设为 nil。 对于 didReceiveMemoryWarning 也一样。
所以,现在让你的 outlet 属性变为 weak 的。 唯一需要声明为 strong 类型的 outlet 属性,就是在 nib 中从 File’s Owner 连接到顶级对象的那个。
概括地说, 属性的修饰符是这样:
在 ARC 之前,你是可以这样写的:
@property (nonatomic, readonly) NSString *result; |
这样会间接的创建一个 assign 属性。 对于 readonly 值没有问题。 但是,如果你对 readonly 的数据使用 retained 意味着什么呢? 在 ARC 中,上面的代码会给出如下错误:
"ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute"
你必须明确的指出,你希望这个属性是 strong, weak 或者 unsafe_unretained 的。 大多数情况下, strong 是合适的答案:
@property (nonatomic, strong, readonly) NSString *result; |
在之前,我提到过如果你定义了一个属性,那么你就应该用 self.propName 的形式来访问它, 而不是通过后端的实例变量(除非是在 init 方法,或者自定义的 getter 和 setter 方法中)。 这对 readonly 属性也适用。 如果你通过实例变量修改这样的属性, ARC 会不知道该怎么办,并且导致奇怪的 bug 出现。 正确的做法是在类扩展中将这个属性重新定义为 readwrite 的:
在 .h 文件中:
@interface WeatherPredictor @property (nonatomic, strong, readonly) NSNumber *temperature; @end |
在 .m 文件中:
@interface WeatherPredictor () @property (nonatomic, strong, readwrite) NSNumber *temperature; @end |
让我们修改最后一个方法,这样我们可以重新运行应用。
这个方法里,用到了 CFURLCreateStringByAddingPercentEscapes() 函数,来对一个字符串进行 URL 编码。 我们用它来确保用户输入的搜索文本中任何的空格和其他字符都能转换成 HTTP GET 请求中有效的内容。
编译器给出了如下错误:
‘autorelease’
最后两个错误是一样的,并且很简单, 我们不能调用 [autorelease]。 先不管他们。
- (NSString *)escape:(NSString *)text { return (NSString *)CFURLCreateStringByAddingPercentEscapes( NULL, (CFStringRef)text, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); } |
其他两个错误和 “bridged” 转换有关系。 这个方法中有3个类型转换:
编译器只对前两个给出了错误。 第三个是对常量对象的转换,所以不需要特别的内存管理。 这个字符串常量会打包到应用的可执行程序中。 和 “真实” 的对象不同, 它不需要分配和释放。
如果你想要处理,你可以这样写:
CFSTR("!*'();:@&=+$,/?%#[]") |
CFSTR() 从一个指定的字符串创建一个 CFStringRef 对象。 这个字符串是一个标准的 C 字符串,没有以 @ 符号开头。 我们没有创建 NSString 对象,并把它转换成 CFStringRef, 而至直接创建一个 CFStringRef 对象。 你喜欢哪一个取决于你的感觉, 他们的结果是一样的。
当你在Core Foundation 和 Cocoa 中转换对象时 Bridged casts 是必须的。
现在大多数的应用都不太需要用到 Core Foundation, 你几乎可以舒服的用 Objective-C 中的类来完成你要的任何工作。 然而,一些基于 Core Foundation 的底层 API, 例如 Core Graphics 和 Core Text 他们没有 Objective-C 版本。 幸运的是, iOS 的设计者让这两个领域中对象的转换变得非常简单。 并且你不需要负责任何操作。
不管是基于任何目的, NSString 和 CFStringRef 都可以看做是一样的。 你可以将 NSString 对象当做 CFStringRef 类型来使用, 并且还可以将 CFStringRef 对象当做 NSString 类型来使用。 这是 toll-free bridging 的思想。 在以前,可以简单的这样写:
CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name]; |
当然,你还要记得,当你用完它的时候把它释放掉:
CFRelease(s1); |
另外一种方法, 从 Core Foundation 到 Objective-C:
CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, bytes, kCFStringEncodingMacRoman); NSString *s3 = (NSString *)s2; // release the object when you're done [s3 release]; |
现在我们有了 ARC, 编译器需要知道谁来负责释放这个转换过的对象。 如果你把一个 NSObject 当做一个 Core Foundation 对象, 那么 ARC 就不再负责释放它。 但你需要告诉 ARC 你的意图, 编译器自己不能推断出来。 同样的, 如果你创建了一个 Core Foundation 对象, 但随后把它转换成了 NSObject, 你需要告诉 ARC 获取它的所有关系, 并在合适的时候释放这个对象。 这正是 bridging casts 要解决的。
让我们首先来看看简单转换。 CFURLCreateStringByAddingPercentEscapes() 函数接收一大串参数, 其中两个是 CFStringRef 对象。 他们在 Core Foundation 中等同于 NSString。 在以前,我们只需要将 NSString 对象转换成 CFStringRef 类型, 但是在 ARC 中, 编译器需要更多信息。 我们已经处理过了常量字符串, 它不需要 bridging cast, 它是一个不需要释放的特殊对象。 但是, text 参数又是另外一回事。
text 变量是一个 NSString 对象,作为一个参数传递给这个方法。 和局部变量一样, 方法参数也是强引用; 这些对象在进入方法时就被 retain 了。 text 所指向的值一直会存在, 直到这个指针被销毁。 因为它是一个局部变量, 在 escape 方法结束是,他就会被销毁。
我们希望 ARC 作为这个对象的所有者, 但我们也希望暂时的将它当做 CFStringRef。 在这种情况下, 会使用到 __bridge 修饰符。 它告诉 ARC 不需要改变所属关系, 对象还是通过默认的规则释放掉。
我们在之前的 SoundEffect.m 中已经用到过 __bridge 了:
OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileURL, &theSoundID); |
这里的情况完全相同。 fileURL 变量指向了一个 被 ARC 管理的 NSURL 对象。 而 AudioServicesCreateSystemSoundID() 函数, 期望的是一个 CFURLRef 对象。 幸好 NSURL 和 CFURLRef 是 toll-free bridged 的, 所以我们可以将其中一个转换为另外一个。 因为我们仍然希望 ARC 在我们操作完成之后释放这个对象, 我们使用 __bridge 关键字来指示 ARC 仍然负责释放。
将 escape: 方法修改成这样:
- (NSString *)escape:(NSString *)text { return (NSString *)CFURLCreateStringByAddingPercentEscapes( NULL, (__bridge CFStringRef)text, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); } |
我们还剩最后一个错误:
大多数时候,当你想在 Objective-C 和 Core Foundation 之间的对象相互转换时, 都会用 __bridge 关键字。 然而,也有时候,你想要把所有关系给 ARC 或者解除 ARC 的所有关系。 在这种情况下, 那么有两个其他的 bridging casts 关键字:
在我们的源文件中,还有一个错误:
return (NSString *)CFURLCreateStringByAddingPercentEscapes( |
如果你点击错误消息, 将弹出下面这样的修复提示:
它给你提供了两个可选方案: __bridge 和 __bridge_transfer. 正确的选择是 __bridge_transfer. CFURLCreateStringByAddingPercentEscapes() 函数将创建一个新的 CFStringRef 对象。 当然, 我们要用 NSString, 所以我们需要类型转换。 我们实际要做的是这样:
CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .); NSString *s = (NSString *)result; return s; |
因为函数名称中包含了 “Create”, 它会返回一个被 retain 的对象。 所以我们需要在合适的时候释放它。 如果我没没有把它当做 NSString 返回, 那么我们的代码看起来应该是这样:
- (void)someMethod { CFStringRef result = CFURLCreateStringByAddingPercentEscapes (. . .); // do something with the string // . . . CFRelease(result); } |
记住 ARC 仅能作用于 Objective-C 对象, 不能应用于 Core Foundation 对象。 你仍然需要对这样的对象调用 CFRelease() 方法。
我们想要做的是在 escape 方法中将 CFStringRef 对象转换为 NSString 对象, 然后 ARC 会在我们不需要它的时候自动释放掉它。 但是 ARC 需要我们告诉它要这样做。 因此, 我们使用 __bridge_transfer 修饰符告诉它,“嘿 ARC, 这个 CFStringRef 对象现在是 NSString对象了, 并且我想要你来释放它, 这样我们就不需要调用 CFRelease() 了”。
这个方法现在变成这样了:
- (NSString *)escape:(NSString *)text { return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes( NULL, (__bridge CFStringRef)text, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); } |
我们或得到 CFURLCreateStringByAddingPercentEscapes() 的返回结果, 一个 CFStringRef 对象, 并且将它转换为 NSString, 现在受控于 ARC。
如果我们仅仅应 __bridge 关键字代替, 那么你的应用将会出现内存泄露。 ARC 不知道在你不需要的时候释放这个对象, 并且没有人调用 CFRelease()。 结果就是,这个对象一直留在内存中。 选择合适的 bridge 修饰符非常重要!
为了更简单的记住应该用哪种类型的 bridge, 有一个工具方法 CFBridgingRelease()。 它和 __bridge_transfer 类型转换是一样的效果, 但是它的意思表达的更加清楚。 escape: 的最终版本是这样:
- (NSString *)escape:(NSString *)text { return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes( NULL, (__bridge CFStringRef)text, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))); } |
我们将 CFURLCreateStringByAddingPercentEscapes() 方法的调用包装在 CFBridgingRelease() 中, 而不是使用 (__bridge_transfer NSString *)。 CFBridgingRelease() 定义为 inline 函数, 所有不比直接的类型转换慢多少。 它命名为 CFBridgingRelease() 只因为你会在需要用 CFRelease() 来和创建新对象平衡的地方用到它。
另外一个需要 bridging casts 的通用框架是 AddressBook 框架。 例如:
- (NSString *)firstName { return CFBridgingRelease(ABRecordCopyCompositeName(...)); } |
所有你调用名称含有 Create,Copy 或 Retain 的 Core Foundation 函数的地方, 你都必须使用 CFBridgingRelease() 来将返回值转移到 ARC 中。
另外一个修饰符 __bridge_retained 又是怎么回事呢? 你要以其他的方式来使用它。 假设你有一个 NSString 并且你需要把它给 Core Foundation API 让他们取得这个字符串对象的所有权。 你不希望 ARC 再去释放这个对象, 因为它会被过度释放, 大多数应用都会崩溃。 另外,使用 __bridge_retained 将这个对象给了 Core Foundation, 这样 ARC 就不再负责释放它了。 例如:
NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name]; CFStringRef s2 = (__bridge_retained CFStringRef)s1; // do something with s2 // . . . CFRelease(s2); |
只要 (__bridge_retained CFStringRef) 转换执行了, ARC 就不再不择释放这个字符串对象了。 如果你在这里用了 __bridge, 这个应用很可能会崩溃。 ARC 将在 Core Foundation 使用完这个对象之前释放它。
这种类型的转换也有一个工具方法 CFBridgingRetain()。 从它的名字可以看出, 它让 Core Foundation 对这个对象进行了一次 retain。 上面的例子最好写成这样:
CFStringRef s2 = CFBridgingRetain(s1); // . . . CFRelease(s2); |
现在,这段代码的意思很明确了。 对 CFRelease() 的调用被 CFBridgingRetain() 方法平衡了。 我怀疑你可能都不会经常用到这种转换。 一下子想想, 我没能想到哪个常用的 API 会需要这样的转换。
注意: 不是所有的 Objective-C 和 Core Foundation 对性爱那个都是 toll-free bridged 的。 例如, CGImage 和 UIImage 就不能互相转换, CGcolor 和 UIColor 也不能。这个页面 列出了不能够互相替换的类型。
__bridge 转换,不仅限于 Core Foundation。 一些API 使用 void * 类型的指针, 可以让你存放任何类型的引用, 不管是 Objective-C 对象,还是 Core Foundation 对象; malloc() 分配的内存, 等等。 void * 表示法的意思是: 这是一个指针, 但是它实际的数据类型可以是任何东西。
如果要将 Objective-C 对象转换成 void * 类型的, 你需要使用 __bridge 转换,例如:
MyClass *myObject = [[MyClass alloc] init]; [UIView beginAnimations:nil context:(__bridge void *)myObject]; |
在这个的动画的代理方法中, 反向的把这个对象转换回来:
- (void)animationDidStart:(NSString *)animationID context:(void *)context { MyClass *myObject = (__bridge MyClass *)context; . . . } |
在下一节中,我们将会看到另一个例子, 那里涉及到在 Cocos2D 中使用 ARC。
总结一下:
这就是 MainViewController 所有要修改的东西。 所有的错误都应该消失了, 现在你可以构建并运行应用了。 这里我没没有涉及到将 AFHTTPRequestOperation 转换到 ARC 。
在以后一段时间,你可能会发现很多你喜欢的第三方库还没有支持 ARC 特性。 维护ARC 和 非ARC 两个版本的库是一件很无趣的事, 所以我希望大多数库的维护者都保持一个版本。 新的库可能会只用 ARC 来写, 但是老的库就很难转换了。 因此, 很可能你的一部分代码是关闭 ARC 的(通过 -fno-objc-arc 编译器标志)。
幸好 ARC 是基于单个文件的, 所以在你开启了 ARC 的项目中结合非 ARC 的代码是没问题的。 因为有时候要为大量的文件禁用 ARC 是比较麻烦的, 我们会在下一节中讨论到一种将非 ARC 第三方库导入项目中的更智能的方式。
转载:http://blog.csdn.net/wang9834664/article/details/8197660