这是iOS 5 盛宴中的第12篇教程! 这篇教程是咱们的新书 iOS 5 By Tutorials 的一个预览章节。 Matthijs Hollemans 是这个章节的做者 – 也是 iOS Apprentice Series 的做者。 但愿你喜欢!github
这篇文章发表自 iOS 教程团队成员 Matthijs Hollemans, 一个经验丰富的 iOS开发者和设计师。web
iOS 5 中最具争议的一个新特性就是 Automatic Reference Counting (自动引用计数), 或者简写为 ARC。 ARC 是 LLVM 3.0 编译器的一个新特性,它完全抛弃了让全部 iOS 开发者由爱生恨的手动内存管理机制。编程
在你的项目中使用 ARC 很是简单。 除了再也不须要调用 retain, release 和 autorelease, 你能够和日常同样的开发。 基本上如此而已。后端
在开启 Automatic Reference Counting 的状况下, 编译器将会自动地在你程序的正确位置插入 retain, release 和 autorelease 。你再也不须要为这些事情操心了, 由于编译器已经为你作了这些事。 实际上, 使用 ARC 是如此的简单,你甚至都不须要读这篇教程。 ;-)数组
可是,若是你还对 ARC 有一些质疑 — 或许你不相信它能正确的处理全部事情, 或者你会认为这样会比手动内存管理执行的速度慢 — 那就继续阅读吧。 教程余下的部分将会解开它神秘的面纱, 并向你展现哪些在使用 ARC 时,如何处理那些不太直观的结果。安全
另外, 咱们将会手把手的教你如何将一个没有使用 ARC 的应用转换成使用 ARC 的。 你能够用这些技术来将你现存的 iOS 项目转换成使用 ARC。 帮助你减小大量的内存处理问题!服务器
你大概已经熟悉如何手工管理内存了, 就像这样:网络
做为一个初学者, 你可能须要一个艰难的过程来了解这个概念, 但在一段时间以后, 它将成为你的第二习惯。 如今你必须老是正确的在 retain 和 release 之间取得平衡。 除非你忘了。
手工内存管理的原则并不难,可是它很是容易出错。 而且这些小错误可能会带来严重的后果。 过分的 release 一个对象而且你的变量指向了一个已经无效的地址,你的应用就会在某一时刻崩溃掉,或者若是你没有在该 release 的时候释放掉内存,你的应用就会将内存占满。
Xcode 中的静态分析是帮你找到这类问题的好帮手, 可是 ARC 作得更好。 经过自动地在合适的位置帮你插入 retain 和 release, 它彻底避免了内存管理中的问题!
有一个重要的一点须要知道的,就是 ARC 是 Objective-C 编译器的一个特性, 全部 ARC 相关的处理都会发生在你构建你的应用的时候。 ARC 不是运行时特性(除了其中的一点, weak 指针系统)。 它也不是你在其余编程语言中所了解的垃圾回收机制。
ARC 所作的事情就是在你编译代码的时候,在那些你须要本身处理内存管理的地方,插入 retain 和 release。 这就使得 ARC 和手工内存管理的速度同样快, 有时还会更快一些, 由于它在后端进行了一些优化操做。
你须要了解的 ARC 的新规则很简单。 在手工内存管理中, 你须要 retain 一个对象,来让他可用。 这再也不须要了, 你所须要作的仅仅是让一个指针指向那个对象。 只要有变量指针指向那个对象, 那么它就会一直在内存中。
当这个指针指向了另一个对象,或者再也不存在的时候, 和它相关联的那个对象会被释放掉。 这对全部类型的变量都适用: 实例变量, 属性, 甚至局部变量。
用全部者的方式来想它,就更好理解了。 当你这样作时,
NSString *firstName = self.textField.text; |
firstName 变量成为一个指向 NSString 对象的指针, 它指向了文本框中的内容。 firstName 变量如今就是这个字符串对象的全部者。
一个对象能够有多个全部者。 直到用户改变了 UITextField 中的内容以前, 它的 text 属性也是这个字符串对象的全部者。 有两个指针指向了同一个对象:
稍后,用户会在文本框中输入一些新的文字, 这时它的 text 属性指向了一个新的字符串对象。 可是最初的那个字符串对象仍是有一个全部者(firstName 变量), 因此它仍然在内存中。
只有当 firstName 也获得一个新的值的时候,或者它超出了做用范围 — 由于它是一个局部变量而且方法到告终尾,或者它是一个实例变量而且拥有它的对象被释放了 — 这时全部关系就失效了。 这个字符串对象再也不有任何全部者, 它的 retain count 减小到 0,这个对象被释放了。
咱们称 firstName 和 textField.text 为 “strong” 类型的指针, 由于他们能保持对象的存活。 默认状况下,全部的实例变量和局部变量都是 strong 指针。
也还有一个 “weak” 指针。 weak 类型的变量也能指向一个对象,可是他们不能成为全部者:
__weak NSString *weakName = self.textField.text; |
weakName 变量和 textField.text 属性指向了同一个字符串对象, 但它不是全部者。 若是文本框的内容改变了, 这个字符串对象就再也不有任何全部者了,而后就被释放了:
这个时候, weakName 变量的值会自动变成 nil。 它也被叫作 “zeroing” weak 指针。
这个特性很是方便,由于它防止了指针指向了被释放的内存。 这种状况会致使不少 bug — 你可能据说过”悬空指针”或”僵尸”这样的术语 — 但多亏了 zeroing weak 指针, 这些再也不是问题了!
你或许不会频繁的使用到 weak 指针。 他们大多在两个父子关系的对象上面比较有用。 父对象会有一个 strong 类型的指针指向子对象 — 所以它就”拥有”了子对象 — 可是为了防止全部关系循环, 子对象仅仅有一个 weak 指针指向父对象。
这样的一个例子是代理模式。 你的控制器拥有一个指向 UITableView 的 strong 指针。 Table View 的 datasource 和 delegate 反过来指向控制器,但用的是 weak 指针。 咱们稍后会详细讨论这个。
下面这个很是有用:
__weak NSString *str = [[NSString alloc] initWithFormat:...]; NSLog(@"%@", str); // will output "(null)" |
这个 string 对象没有任何全部者(由于 str 是 weak 的), 这个对象将会在建立后直接被释放掉。 Xcode 将会给你一个警告, 由于这或许不是你想要的结果 (“Warning: assigning retained object to weak variable; object will be released after assignment”)。
你能够用 __strong 关键字来表示一个变量是 strong 类型的指针:
__strong NSString *firstName = self.textField.text; |
可是由于变量默认就是 strong 的,这样作是多余的。
属性能够是 strong 的,也能够是 weak 的。 属性的表示方法以下:
@property (nonatomic, strong) NSString *firstName; @property (nonatomic, weak) id <MyDelegate> delegate; |
ARC 很强大, 能够真正的去掉你代码中杂乱的部分。 你再也不须要考虑何时 retain 仍是 release, 只须要知道你的对象如何和其余对象关联起来。 你须要问一下你本身: 谁拥有什么?
例如,在之前不可能像这样写代码:
id obj = [array objectAtIndex:0]; [array removeObjectAtIndex:0]; NSLog(@"%@", obj); |
在手工内存管理机制下, 从数组中删除这个对象将会让 obj 变量的内容失效。 一旦这个对象从数组中被删除掉,它就会被释放。 经过 NSLog() 来打印这个对象会致使应用崩溃。 在 ARC 中,上面的代码会正常的工做。 由于咱们把这个对象赋值给 obj 变量, 它是一个 strong 指针, 数组再也不是这个对象惟一的全部者。 即便咱们从数组中把这个对象删除掉, 这个对象仍然有效, 由于 obj 还在指向它。
Automatic Reference Counting 也还有一些不足。 做为刚刚起步的特性, ARC 仅仅适用于 Objective-C 对象。 若是你的应用用到了 Core Foundation 或者 malloc() 和 free(), 你还须要负责内存管理。 咱们将会在这篇教程后面的部分看到一些例子。 另外, 为了让 ARC 正确的工做,一些语言规则会变得更严格。 仅有小小的牺牲, 你获得的会比你付出的更多!
正由于 ARC 帮你在适当的位置处理了 retain 和 release, 但这并不表明你能够彻底忘记内存管理机制。 由于 strong 指针可以保持对象存活, 仍然存在一些状况,你须要手动的把这些指针设置为 nil, 否则你的应用将会耗尽可用内存。若是你一直保持全部你建立的对象都存活,那么 ARC 将不能释放他们。 所以, 当你建立一个新对象的时候, 你还须要考虑谁拥有它,还有这个对象应该存活多久。
毫无疑问,ARC 将会是 Objective-C 的将来。 苹果鼓励开发者放弃手工内存管理,从而开始用 ARC 来写他们的新应用。 它能带来更简单的源代码和更健壮的应用。 在 ARC 中, 内存相关的崩溃已经成为了过去。
可是,由于咱们成处于一个从手工向自动内存管理过渡的阶段, 你会常常碰到那些还不兼容 ARC 的代码, 不管是你本身的代码,仍是第三方库的。 幸运的是,你能够将 ARC 和非 ARC 的代码共同用于一个项目,我将给你展现几个怎样实现的方法。
ARC 甚至能和 C++ 很好的结合。 只须要遵照不多的一些限制,你还能够在 iOS 4 中使用 ARC, 这个仅仅用于帮助加快它的采用进程。
一个聪明的开发者会试着尽量的让他的工做自动化。 这正是 ARC 所提供的: 将你以前那些必须手动处理的初级编程工做自动化起来。 对我来讲,这样的转变很容易。
为了展现如何在实践中使用 Automatic Reference Counting。 我准备了一个简单的项目,咱们将把它从手工内存管理转换到 ARC。 这个应用,Artists, 由一个Table View 和一个 Search Bar 组成的界面。 当你在Search Bar 中输入一些东西时, 这个应用调用 MusicBrainz API 来根据姓名搜索音乐家。
这个应用看起来是这样:
用他们本身的话说,MusicBrainz 是”一个开放的音乐百科全书, 为公众提供音乐的元数据”。 他们提供一个免费的 XML web service 来供你的应用调用。 若是像更多的了解 MusicBrainz, 能够去他们的网站看看http://musicbrainz.org.
下载这个教程的起始项目, 并用 Xcode 打开它。 这个项目包含下面的源文件:
另外, 这个应用用到了两个第三方库。 你的应用可能会用到一些外部组件, 这也正好能够学习如何用 ARC 来处理这些库。
让咱们快速的浏览一遍控制器的代码, 这样你能全面的了解应用是如何工做的。 MainViewController 是 UIViewController 的子类。 它的 nib 文件包含了一个 UITableView 对象和一个 UISearchBar 对象:
Table View 显示了 searchResults 数组中的内容。这个指针初始值是 nil。 当用户进行了一次搜索, 咱们用 MusicBrainz 服务器响应的数据来填充这个数组。 若是没有相关的搜索结果,这个数组将会是空的(但不是 nil), Table 上面会显示 “(Nothing found)”。 这些功能都是经过 UITableViewDataSource 的方法完成的:numberOfRowsInSection 和 cellForRowAtIndexPath。
实际的搜索过程是在 searchBarSearchButtonClicked 中进行的, 它是 UISearchBarDelegate 协议的一部分。
- (void)searchBarSearchButtonClicked:(UISearchBar *)theSearchBar { [SVProgressHUD showInView:self.view status:nil networkIndicator:YES posY:-1 maskType:SVProgressHUDMaskTypeGradient]; |
首先,咱们建立了一个新的 HUD, 而且将他显示在 TableView 和 Search bar 的上面, 在网络请求完成以前, 阻止用户的任何输入:
而后咱们建立 HTTP 请求的 URL。 咱们用 MusicBrainz 的 API 来搜索艺术家。
NSString *urlString = [NSString stringWithFormat: @"http://musicbrainz.org/ws/2/artist?query=artist:%@&limit=20", [self escape:searchBar.text]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]]; |
要搜索的文字经过 escape 方法进行 URL 编码: 保证咱们的 URL 是有效的。 空格和其余特殊符号会转换成相似这样的形式: %20 。
NSDictionary *headers = [NSDictionary dictionaryWithObject: [self userAgent] forKey:@"User-Agent"]; [request setAllHTTPHeaderFields:headers]; |
咱们为 HTTP 请求添加了一个自定义的 User-Agent 头。 MusicBrainz API 须要它。 全部的请求都应该 “有一个合适的 User-Agent 请求头,以便用来标识发送请求的应用和版本。” 和你正在用的 API 配合好老是一个好主意, 因此咱们像这样构建了一个 User-Agent 请求头:
com.yourcompany.Artists/1.0 (unknown, iPhone OS 5.0, iPhone Simulator, Scale/1.000000) |
()
(I took this formula from another part of the AFNetworking library and put it into the userAgent method in the view controller.)
MusicBrainz API 还有一些其余的规则。 客户端应用每秒只能发送一个请求到 web service 中, 不然它们的 IP 有可能被屏蔽。 这对咱们的应用来讲不是个大问题 — 用户不太可能作这么屡次的搜索 — 因此咱们不须要预防这类操做。
当咱们建立完 NSMutableURLRequest 对象, 咱们把它发给 AFHTTPRequestOperation 来处理:
AFHTTPRequestOperation *operation = [AFHTTPRequestOperation operationWithRequest:request completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) { // ... }]; [queue addOperation:operation]; |
AFHTTPRequestOperation 是 NSOperation 的一个子类, 这也意味着咱们能够把它添加到 NSOperationQueue(在 queue 变量上) 中, 而且它会进行异步处理。 由于用到了 HUD, 当正在请求数据的时候,应用忽略了全部的用户输入。
咱们给 AFHTTPRequestOperation 指定了一个 block, 当请求完成的时候它会被调用。 在 block 中,咱们首先检测请求是否成功(经过 HTTP 状态码 200)。 在这个应用中咱们对为何失败不感兴趣; 若是失败了,咱们仅仅让 HUD 以一个”失败”动画消失。 注意 completion block 不必定会在主线程上执行, 因此咱们须要将对 SVProgressHUD 的调用包装到 dispatch_async() 中。
if (response.statusCode == 200 && data != nil) { . . . } else // something went wrong { dispatch_async(dispatch_get_main_queue(), ^ { [SVProgressHUD dismissWithError:@"Error"]; }); } |
如今开看看有趣的部分。 若是请求成功了, 咱们建立 searchResults 数组,而且解析响应。 服务端返回的数据是 XML 格式的,因此咱们用 NSXMLParser 来解析它。
self.searchResults = [NSMutableArray arrayWithCapacity:10]; NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; [parser setDelegate:self]; [parser parse]; [parser release]; [self.searchResults sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; |
你能够在 NSXMLParserDelegate 的方法中找到解析 XML 的逻辑代码, 咱们实际上仅仅查找了名称为 “sort-name” 的元素。 它们包含了艺术家的姓名。 咱们将这些姓名以 NSString 对象的形式放入 searchResults 数组。 当 XML 解析完成时, 咱们以字母表顺序对这个数组进行排序, 而且在主线程中更新界面显示:
dispatch_async(dispatch_get_main_queue(), ^ { [self.soundEffect play]; [self.tableView reloadData]; [SVProgressHUD dismiss]; }); |
这些就是这个应用如何工做的。 它使用手工内存管理,而且没有用到任何 iOS 5 的特性。 如今,让咱们把它转换到 ARC 吧。
咱们将要把 Artists 应用转换到 ARC。 简单来讲, 咱们再也不须要调用 retain, release 和 autorelease 了, 但在一些特定状况下,咱们还须要作一些特殊处理。
这里有三个方式能让你的应用作到 ARC 兼容:
咱们将在 Artists 应用中用到全部这些操做, 仅为了向你展现全部这些是如何工做的。 在这个部分, 咱们将会经过 Xcode 的自动转换工具来转换源文件, 除了 MainViewController 和 AFHTTPRequestOperation。
在咱们作这些事情以前, 你应该把项目拷贝一份,由于这个工具会覆盖原来的文件。 Xcode 会提供一个对源代码的预览界面, 可是为了防止丢失,我不管如何也会备份一份。
ARC 是 LLVM 3.0 的一个新特性。 你现有的项目极可能用的是老的 GCC 4.2 或者 LLVM-GCC 编译器, 因此首先要作的是将项目的编译器切换到新版本,看一看编译器是否在非 ARC 模式。 进入 Project Settings 界面,
选择 Artists target, 在 Build Settings 选项卡中的搜索框中输入 “compiler”. 这样能够过滤列表,展现出编译选项:
点选 Compiler for C/C++/Objective-C 选项,修改它为 Apple LLVM compiler 3.0:
在 Warnings 头中,还要把 Other Linker Flags 设置为 -Wall。 这样编译器将会检测全部会致使问题的状况。 默认状况下,这些警告消息是被关闭的,可是我发现老是把他们打开而且将每个都看做是致命错误是颇有用的。 换句话说, 若是编译器给出任何警告,我将会在继续其余工做以前修复它。 是否在你本身的项目中也这样作彻底取决于你, 可是在转换到 ARC 的过程当中, 我推荐你仔细看看编译器给出的每个问题。
一样地, 也要在 Build Options 头中打开 Run Static Analyzer:
Xcode 如今将会在每次构建项目的时候运行静态分析。 这会让构建的速度稍微慢一点, 可是做为咱们这种规模的应用来讲,这不算什么。
让咱们来构建一下应用, 看看新的编译器会给出什么问题. 首先咱们用 Product -> Clean(或 Shift-Cmd-K) 进行一次清理。 而后按下 Cmd-B 来构建应用。 Xcode 应该不会收到任何警告。 若是你在将你本身的项目转换到 ARC, 而且收到了警告消息, 那么如今正是修复他们的时候。
让咱们把编译器切换到 ARC 模式,而且再次构建应用。 咱们收到了一大堆错误消息, 如今正好能够看看这些到底是什么。
仍然在 Build Settings 屏幕中, 切换到 “All” 能够看到全部可用的设置(Basic选项仅仅显示最经常使用到的设置)。 搜索 “automatic”, 设置 Objective-C Automatic Reference Counting 选项为 YES。 设置一个项目范围的标记, 用来告诉 Xcode 你将要用 ARC 编译器来编译你的项目中全部的源文件。
再次构建应用, 你应该会看到一大堆错误:
很明显,咱们要进行一些迁移! 大多数的错误都很明显,他们说的都是你不能再用 retain,release 和 autorelease 了。 咱们能够彻底手工的修正这些错误, 可是使用自动转换工具会更容易一些。 这个工具会用 ARC 模式来编译应用, 而后对每个它遇到的错误的地方进行重写,直到项目再也不报错。
在 Xcode 菜单中, 选择 EditRefactorConvert to Objective-C ARC.。
一个新的窗口会弹出来, 让你选择哪些部分是你想要转换的:
咱们不但愿转换整个项目, 只选择下面这些文件:
这个对话框显示了一个小警告图标,用来指示这个项目已经使用了 ARC。 这是由于咱们以前在 Build Settings 中开启了 Objective-C Automatic Reference Counting 选项, 因此自动转换工具会认为它已是一个 ARC 项目了。 你能够忽略这个警告, 他不会对转换形成影响。
按下 Precheck 按钮来开始转换。 这个工具首先会检测你的代码是否处于足够好的状态来转换到 ARC。 咱们以前用新的 LLVM 3.0 编译器成功的构建了咱们的应用, 可是很明显此次不行。 Xcode 会给出以下错误:
它提示 “ARC readiness issues”, 咱们应该开启 “Continue building after errors” 选项。 咱们应该先开启这个选项。 打开 Xcode 的 Preferences 窗口 (在 Xcode 的菜单中), 进入 General 标签。 开启选项 “Continue building after errors”:
让咱们再试一次。选择 EditRefactorConvert to Objective-C ARC 而且选中除了 MainViewController.m 和 AFHTTPRequestOperation.m 以外的全部源文件。 按下 Precheck 按钮。
很不幸, 咱们再一次获得了一个错误。 和刚才不一样的是此次编译器可以在转换以前找出咱们须要修复的全部问题。 幸运的是, 只有一个错误:
你可能会在这个列表中看到更多的错我。 有时转换工具会提示那些不彻底是 “ARC readiness” 性的错误。
咱们这个问题的完整描述是:
Cast of Objective-C pointer type 'NSURL *' to C pointer type 'CFURLRef' (aka 'const struct __CFURL *') requires a bridged cast |
在源代码编辑器中它看起来是这样的:
我会在后面更详细的讨论它, 但这里的代码想尝试着把 NSURL 对象转换成 CFURLRef 对象。 AudioServicesCreateSystemSoundID() 函数接受一个 CFURLRef 参数, 用来描述声音文件的位置, 可是咱们给他的是一个 NSURL 对象。 CFURLRef and NSURL 是 “可互换的”, 这样能够在须要 CFURLRef 的地方使用 NSURL, 反过来也同样。
通常状况下, iOS 中基于 C 语言的 API 会用到 Core Foundation 对象 (这也是 CF 的意思), 而基于 Objective-C 的 API 用的是继承自 NSObject 的”真正的”对象。 有时你须要在这二者之间进行转换, 这也是 “可互换的” 对象所表明的意思。
然而, 当你使用 ARC 的时候,编译器须要知道应该怎样处理 “可互换的” 这些对象。 若是你在须要 CFURLRef 的地方使用了 NSURL, 那么谁负责在最后释放这些内存呢? 为了解决这个难题, 一系列的新关键字被引入进来:
__bridge, __bridge_transfer 和 __bridge_retained。 咱们会在接下来的教程中深刻的了解如何使用它们。
如今咱们须要按照下面的方式修改源代码:
OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef) fileURL, &theSoundID); |
预检测可能会给出不止这一个错误。 你能够安全的忽略它们, 上面对 SoundEffect.m 的修改是惟一一个咱们要作的。 转换工具看起来对把什么当作 “ARC 先决条件问题” 也不是很肯定。
让咱们再运行一次转换工具 – EditRefactorConvert to Objective-C ARC。 此次预检测运行的没有任何问题, 咱们将会获得这样的界面:
点击 Next 继续。 几秒钟以后, Xcode 将会为他要修改的全部文件和在这些文件上面进行的修改内容显示一个预览界面。 左边的面板显示的是修改过的文件,右边的面板显示的是原始文件。
一个好的习惯是浏览一下全部这些文件,确保 Xcode 不会弄乱任何东西。让咱们从头至尾看一遍转换工具给咱们展现的修改内容吧。
@property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) MainViewController *viewController; |
App Delegate 有两个属性, 一个是 Window, 另外一个是 View Controller。 这个项目没有包含 MainWindow.xib 文件, 因此这两个对象由 AppDelegate 在 application:didFinishLaunchingWithOptions: 方法中建立, 并存放到属性中以便进行内存管理。
这些属性的声明修改以下:
@property (retain, nonatomic) |
修改成:
@property (strong, nonatomic) |
strong 关键字和你想象的同样。 它告诉 ARC 这个属性对应的 synthesize 声明的实例变量,持有一个当前对象的强引用。 换句话说, window 属性包含了一个指向 UIWindow 对象的指针,而且做为这个 UIWindow 对象的全部者。 只要 window 属性还保留着它的值, 那么这个 UIWindow 对象就一直存活。 viewController 属性和 MainViewController 对象也是同样的。
在 AppDelegate.m 中, 建立 window 和 view controller 对象的代码被修改了, 而且 dealloc 方法彻底被删除了:
看看他们之间不一样的地方,
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; |
和:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; |
这是没问题的, 再也不须要调用 autorelease 了。 建立 view controller 的代码也是同样。
self.viewController = [[[MainViewController alloc] initWithNibName: @"MainViewController" bundle:nil] autorelease]; |
如今变成了:
self.viewController = [[MainViewController alloc] initWithNibName: @"MainViewController" bundle:nil]; |
在使用 ARC 以前, 若是你像这样写代码,若是这个属性声明成 “retain” 类型的, 那么就会形成内存泄露:
self.someProperty = [[SomeClass alloc] init]; |
init 方法返回了一个被 retain 过的对象,又把它赋值给声明为 retain 的属性。 这也是为何你必需要调用 autorelease, 用来和 init 方法中的 retain 进行平衡。 可是在 ARC 中,上面的代码是没问题的。 编译器足够的只能,能够发现它不该该被 retain 两次。
ARC 一个让我喜欢的地方是它彻底再也不须要写 dealloc 方法了。 当一个对象被释放时, 它的实例变量和属性也会自动跟着释放。 你再也不须要写这些东西了:
- (void)dealloc { [_window release]; [_viewController release]; [super dealloc]; } |
由于 Objective-C 自动的处理了这些问题。 事实上, 像上面这样写代码也是不可能的。 在 ARC 中,你是不容许调用 release 和 [super dealloc] 消息的。 你仍然能够实现 dealloc 方法 — 而且后面你会看到一个例子 — 但这里再也不须要手动的释放你的实例变量。
自动转换工具不会帮你把 AppDelegate 从 NSObject 的子类转换为 UIResponder 的子类。 当你用新的 Xcode 模板来建立项目的时候, AppDelegate 类已是 UIResponder 的子类了。 让他继承自 NSObject 也没什么坏处, 可是若是你愿意,你能够将它继承自 UIResponder:
@interface AppDelegate : UIResponder <UIApplicationDelegate> |
在手工内存管理的应用中, [autorelease] 方法和 “autorelease pool” 一同工做, 它用 NSAutoreleasePool 对象来表示。 甚至 main.m 中也有一个, 若是你直接操做线程, 那么必须为每一个线程建立一个你本身的 NSAutoreleasePool。 有时,开发者还会将 NSAutoreleasePool 放到一个工做量很大的循环中, 用来保证循环中的自动释放对象不会占据很大的内存,而且每次循环完成后都会被清楚掉。
在 ARC 中 autorelease 依然存在,即便你不须要直接的调用 [autorelease] 方法。 每次你用不是以 alloc, init, copy, mutableCopy 或 new 开头的方法返回一个对象时, ARC 编译器会自动帮你调用 autorelease 方法。 这些对象还会在 autorelease pool 中被释放。 一个比较大的差异是 NSAutoreleasePool 被写成了一个新的语法, @autoreleasepool。
自动转换工具把咱们的 main() 函数从这样:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); [pool release]; return retVal; |
转换成这样:
@autoreleasepool { int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); return retVal; } |
不只对咱们开发人员来讲,它更容易阅读,并且在它的内部也进行了不少修改, 让这个新的 autorelease pool 比以前的快不少。 除非你在以前本身的代码中用到了 NSAutoreleasePool, 在 ARC 中你几乎不用再关心 autorelease 了, 你须要把它替换成 @autoreleasepool 代码块。 转换工具会自动的帮你完成这个工做。
这个文件没怎么修改, 只是删除了 [super dealloc] 的调用。 你不能再在 dealloc 方法中调用 super 了。
注意 dealloc 方法在这里仍然是必须的。 在你的大多数类中你能够直接忘掉 dealloc, 让编译器替你处理这些事情。 然而,有时候, 你须要手工的释放一些资源。 这个类就属于这种状况。 当 SoundEffect 被释放的时候, 咱们须要调用 AudioServicesDisposeSystemSoundID() 方法来清理 sound 对象并释放它。
这个文件是这几个里面修改最多的, 但都比较琐碎。
在 SVProgressHUD.m 的顶部,你须要找到称为 “类扩展” 的代码, @interface SVProgressHUD (), 这里有一些属性的声明。 若是你不熟悉类扩展, 他们其实和 Category 很像,但有一些特殊的能力。 类扩展的定义和 Category 很类似, 可是它在 () 之间没有任何名字。 类扩展能够带有属性和实例变量, 而 Category 没有这些, 你仅仅能在这个类的 .m 文件中引用它们。 (话句话说,你不能在其余类中引用这个类扩展) 。
类扩展最酷的地方是,它能让你在你的类中增长私有属性和方法。 若是你不想在 public @interface 中暴露一些属性和方法, 那么你就能够把它们放到类扩展中。 这也是 SVProgressHUD 作的事情。
@interface SVProgressHUD () ... @property (nonatomic, strong) NSTimer *fadeOutTimer; @property (nonatomic, strong) UILabel *stringLabel; @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UIActivityIndicatorView *spinnerView; ... @end |
像咱们以前看到的, retain 的属性如今变成了 strong。 若是你浏览一下预览视图, 你会看到其余的更改只是简单的删除掉 retain 和 release 语句。
若是你对转换工具作的修改还满意, 按下 Save 按钮,让他生效。 Xcode 首先会问题是否但愿在进行文件修改以前保存一下项目的快照:
你应该点 Enable。 若是你退回到原来的代码, 你能够在 Organizer 中找到项目的快照。
当 ARC 转换工具完成后, 按下 Cmd+B 来构建应用。 构建应该成功的完成。 但会有一些关于 SVProgressHUD.m 的新警告:
注意到在这个类中仍然用到了 dealloc 方法, 在这里中止了 timer 而且从 NSNotificationCenter 中注销了通知。 很明显,这些是 ARC 不能帮你作的。
包含警告的这行代码看起来是这样:
if(fadeOutTimer != nil) [fadeOutTimer invalidate], [fadeOutTimer release], fadeOutTimer = nil; |
如今是:
if(fadeOutTimer != nil) [fadeOutTimer invalidate], fadeOutTimer, fadeOutTimer = nil; |
工具删除了对 [release] 的调用, 可是它把变量还留在那。 一个单独的变量放在那没有任何用处,因此 Xcode 发出了警告。 这种状况自动转换工具是不能预知的。
若是你对逗号这种语法看到困惑, 那么你要知道,在 Objective-C 用逗号将多个表达式链接成一个语句是有效的。 上面是一个释放对象和设置相应的实例变量为空时的一个通用技巧。 由于全部事情都在一行代码中处理了。 因此不须要花括号。
你能够修改这行代码来消除警告:
if(fadeOutTimer != nil) [fadeOutTimer invalidate], fadeOutTimer = nil; |
从技术上说咱们不须要这个 fadeOutTimer = nil; 在 dealloc 中, 对象会在被删除时释放它全部的实例变量。 但在其余的方法中, 当 timer 失效以后, 你必须设置 fadeOutTimer 为 nil。 若是你不这样作, SVProgressHUD 会长时间等待这个失效的 timer。
再次构建应用, 此次不会有任何警告。 转换成功了!
可是,等一下, 咱们在转换时跳过了 MainViewController 和 AFHTTPRequestOperation。 他们是如何在编译过程当中没有任何问题的? 当咱们以前试着用 ARC 来构建项目时, 在这些文件中会出现不少错误。
答案很简单: 转换工具为这两个源文件关闭了 ARC 功能。 你能够在 Project Settings 的 Build Phases 标签中看到他们:
咱们以前经过在 Build Settings 中设置 Objective-C Automatic Reference Counting 选项为 YES, 在项目全局开启了 ARC。 可是你也能够告诉编译器一些例外,让它忽略一些指定的文件, 经过 -fno-objc-arc 标记。 Xcode 将会为这些文件关闭 ARC。
由于,不可能期待开发者一次性的将整个项目迁移到 ARC 中, Apple 的员工们让 ARC 和非 ARC 的代码能够在同一个项目中工做。 提示: 一个简单的方式是,直接经过转换工具迁移那些你想转换的文件,而后它会自动为其他的文件增长 -fno-objc-arc 标记。 你也能够手工的添加这些标记, 但当你有不少文件的时候,这会很无聊。