这往往是引用的问题。
ARC要求完整的前向引用,也就是说在MRC时代可能仅仅须要在.h中申明@class就能够,可是在ARC中假设调用某个子类中未覆盖的父类中的方法的话。必须对父类.h引用,否则无法编译。
有一篇文章讲的非常具体
本文部分实例取自iOS 5 Toturail一书中关于ARC的教程和公开内容。仅用于技术交流和讨论。
请不要将本文的部分或所有内容用于商用,谢谢合作。
欢迎转载本文。可是转载请注明本文出处:http://www.onevcat.com/2012/06/arc-hand-by-hand/
本文适合人群:对iOS开发有一定基础。熟悉iOS开发中内存管理的Reference Counting机制,对ARC机制有听闻非常向往可是一直因为种种原因没有使用的童鞋。本文将从ARC机理入手对这个解放广大iOS开发人员的伟大机制进行一个剖析,并逐步引导你開始使用ARC。一旦习惯ARC。你一定会被它的简洁高效所征服。
写在开头
尽管距离WWDC2011和iOS 5已经快一年时间。可是非常多开发人员并没有利用新方法来提高自己的水平,这点在ARC的使用上非常明显(特别是国内。基本非常少见到同行转向ARC)。我以前询问过一些同行为什么不转向使用ARC,非常多人的回答是操心内存管理不受自己控制..事实上我个人觉得这是对于ARC机制了解不足从而不自信。所导致的对新事物的恐惧。而作为最须要“追赶时髦”的职业,这种心态将相当不利。谨以此文希望能清楚表述ARC的机理和使用方法。也希望可以成为如今中文入门教学缺失的补充。
什么是ARC
Automatic Reference Counting,自己主动引用计数,即ARC,能够说是WWDC2011和iOS5所引入的最大的变革和最激动人心的变化。ARC是新的LLVM 3.0编译器的一项特性,使用ARC,能够说一举攻克了广大iOS开发人员所憎恨的手动内存管理的麻烦。
在project中使用ARC很easy:仅仅须要像往常那样编写代码,仅仅只是永远不写retain
,release
和autorelease
三个keyword就好~这是ARC的基本原则。当ARC开启时,编译器将自己主动在代码合适的地方插入retain
, release
和autorelease
,而作为开发人员,全然不须要操心编译器会做错(除非开发人员自己错用ARC了)。好了,ARC相当简单吧~到此为止,本教程结束。
等等…或许还有其它问题,最严重的问题是“我怎么确定让ARC来管理不会出问题?”或者“用ARC会让程序性能下降吧”。对于ARC不能正处理内存管理的质疑自从ARC出生以来就一直存在。而如今越来越多的代码转向ARC并取得了非常好的效果,这证明了ARC是一套有效的简化开发复杂程度的机制。另外通过研究ARC的原理,能够知道使用ARC甚至能提高程序的效率。在接下来将详解ARC的执行机理而且提供了一个step-by-step的教程,将非ARC的程序转换为ARC。
ARC工作原理
手动内存管理的机理大家应该已经很清楚了,简单来说。仅仅要遵循下面三点就能够在手动内存管理中避免绝大部分的麻烦:
假设须要持有一个对象,那么对其发送retain 假设之后不再使用该对象,那么须要对其发送release(或者autorealse) 每一次对retain,alloc或者new的调用。须要相应一次release或autorealse调用
刚開始学习的人可能只不过知道这些规则,可是在实际使用时难免犯错。可是当开发人员常常使用手动引用计数 Manual Referecen Counting(MRC)的话,这些规则将逐渐变为本能。你会发现少一个release
的代码怎么看怎么别扭,从而降低或者杜绝内存管理的错误。
能够说MRC的规则很简单,可是同一时候也很easy出错。
往往很小的错误就将引起crash或者OOM之类的严重问题。
在MRC的年代里,为了避免不小心忘写release
,Xcode提供了一个非常有用的小工具来帮助可能存在的代码问题(Xcode3里默认快捷键Shift+A?不记得了),能够指出潜在的内存泄露或者过多释放。
而ARC在此基础上更进一步:ARC是Objective-C编译器的特性。而不是执行时特性或者垃圾回收机制,ARC所做的仅仅只是是在代码编译时为你自己主动在合适的位置插入release
或autorelease
,就如同之前MRC时你所做的那样。因此。至少在效率上ARC机制是不会比MRC弱的,而由于能够在最合适的地方完毕引用计数的维护,以及部分优化。使用ARC甚至能比MRC取得更高的执行效率。
ARC机制
学习ARC非常easy,在MRC时代你须要自己retain
一个想要保持的对象,而如今不须要了。
如今唯一要做的是用一个指针指向这个对象,仅仅要指针没有被置空。对象就会一直保持在堆上。当将指针指向新值时,原来的对象会被release
一次。
这对实例变量,synthesize的变量或者局部变量都是适用的。比方
NSString *firstName = self.textField.text;
firstName
如今指向NSString对象,这时这个对象(textField
的内容字符串)将被hold住。比方用字符串@“OneV"作为样例(尽管实际上不应该用字符串举样例。由于字符串的retainCount规则事实上和普通的对象不一样,大家就把它当作一个普通的对象来看吧…)。这个时候firstName
持有了@"OneV"。
当然,一个对象能够拥有不止一个的持有者(这个类似MRC中的retainCount>1的情况)。在这个样例中显然self.textField.text
也是@“OneV",那么如今有两个指针指向对象@"OneV”(被持有两次。retainCount=2。事实上对NSString对象说retainCount是有问题的,只是anyway~就这个意思而已.)。
过了一会儿。或许用户在textField
里输入了其它的东西,那么self.textField.text
指针显然如今指向了别的字符串,比方@“onevcat",可是这时候原来的对象已然是存在的。由于另一个指针firstName
持有它。如今指针的指向关系是这种:
仅仅有当firstName
也被设定了新的值。或者是超出了作用范围的空间(比方它是局部变量可是这种方法运行完了或者它是实例变量可是这个实例被销毁了),那么此时firstName
也不再持有@“OneV",此时不再有指针指向@"OneV",在ARC下这样的状况发生后对象@"OneV"即被销毁,内存释放。
类似于firstName
和self.textField.text
这种指针使用keywordstrong
进行标志,它意味着仅仅要该指针指向某个对象,那么这个对象就不会被销毁。反过来说,ARC的一个基本规则即是。仅仅要某个对象被任一strong
指针指向。那么它将不会被销毁。
假设对象没有被不论什么strong指针指向。那么就将被销毁。
在默认情况下,全部的实例变量和局部变量都是strong
类型的。
能够说strong
类型的指针在行为上和MRC时代retain
的property是比較相似的。
既然有strong
,那肯定有weak
咯~weak
类型的指针也能够指向对象,可是并不会持有该对象。
比方:
__weak NSString *weakName = self.textField.text
得到的指向关系是:
这里声明了一个weak
的指针weakName
。它并不持有@“onevcat"。
假设self.textField.text
的内容发生改变的话。依据之前提到的"仅仅要某个对象被任一strong指针指向,那么它将不会被销毁。假设对象没有被不论什么strong指针指向。那么就将被销毁”原则。此时指向@“onevcat"的指针中没有strong
类型的指针,@"onevcat"将被销毁。同一时候,在ARC机制作用下,全部指向这个对象的weak
指针将被置为nil
。这个特性相当实用,相信无数的开发人员都以前被指针指向已释放对象所造成的EXCBADACCESS困扰过,使用ARC以后,不论是strong
还是weak
类型的指针。都不再会指向一个dealloced的对象,从根源上攻克了意外释放导致的crash。
只是在大部分情况下,weak
类型的指针可能并不会非经常常使用。比較常见的使用方法是在两个对象间存在包括关系时:对象1有一个strong
指针指向对象2,并持有它。而对象2中仅仅有一个weak
指针指回对象1,从而避免了循环持有。一个常见的样例就是oc中常见的delegate设计模式。viewController中有一个strong
指针指向它所负责管理的UITableView。而UITableView中的dataSource
和delegate
指针都是指向viewController的weak
指针。能够说,weak
指针的行为和MRC时代的assign
有一些相似点,可是考虑到weak
指针更聪明些(会自己主动指向nil),因此还是有所不同的。
细节的东西我们稍后再说。
注意类似以下的代码似乎是没有什么意义的:
__weak NSString *str = [[NSString alloc] initWithFormat:…];
NSLog(@"%@",str); //输出是"(null)"
由于str
是weak
。它不会持有alloc出来的NSString
对象。因此这个对象由于没有有效的strong
指针指向,所以在生成的同一时候就被销毁了。假设我们在Xcode中写了上面的代码。我们应该会得到一个警告,由于不管何时这样的情况似乎都是不太可能出现的。你能够把weak换成strong来消除警告。或者直接前面什么都不写,由于ARC中默认的指针类型就是strong
。
property也能够用strong
或weak
来标记,简单地把原来写retain
和assign
的地方替换成strong
或者weak
就能够了。
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, weak) id delegate;
ARC能够为开发人员节省非常多代码,使用ARC以后再也不须要关心什么时候retain
。什么时候release
,可是这并不意味你能够不思考内存管理,你可能须要常常性地问自己这个问题:谁持有这个对象?
比方以下的代码,如果array
是一个NSMutableArray
而且里面至少有一个对象:
id obj = [array objectAtIndex:0];
[array removeObjectAtIndex:0];
NSLog(@"%@",obj);
在MRC时代这几行代码应该就挂掉了,由于array
中0号对象被remove以后就被马上销毁了,因此obj指向了一个dealloced的对象。因此在NSLog的时候将出现EXCBADACCESS。而在ARC中由于obj是strong
的。因此它持有了array
中的首个对象。array
不再是该对象的唯一持有者。即使我们从array
中将obj移除了,它也依旧被别的指针持有。因此不会被销毁。
一点提醒
ARC也有一些缺点。对于刚開始学习的人来说,可能仅仅仅能将ARC用在objective-c对象上(也即继承自NSObject的对象),可是假设涉及到较为底层的东西,比方Core Foundation中的malloc()或者free()等,ARC就鞭长莫及了,这时候还是须要自己手动进行内存管理。在之后我们会看到一些这方面的样例。另外为了确保ARC能正确的工作,有些语法规则也会由于ARC而变得略微严格一些。
ARC确实能够在适当的地方为代码加入retain
或者release
,可是这并不意味着你能够全然忘记内存管理,由于你必须在合适的地方把strong
指针手动设置到nil。否则app非常可能会oom。
简单说还是那句话。你必须时刻清醒谁持有了哪些对象,而这些持有者在什么时候应该变为指向nil
。
ARC必定是Objective-C以及Apple开发的趋势,今后也会有越来越多的项目採用ARC(甚至不排除MRC在未来某个版本号被弃用的可能)。Apple也一直鼓舞开发人员開始使用ARC,由于它确实能够简化代码并增强其稳定性。能够这么说,使用ARC之后,由于内存问题造成的crash基本就是过去式了(OOM除外 :P)
我们正处于由MRC向ARC转变的节点上,因此可能有时候我们须要在ARC和MRC的代码间来回切换和适配。Apple也想到了这一点。因此为开发这提供了一些ARC和非ARC代码混编的机制,这些也将在之后的样例中列出。另外ARC甚至能够用在C++的代码中。而通过遵守一些代码规则。iOS 4里也能够使用ARC(尽管我个人觉得在如今iOS 6都呼之欲出的年代已经基本没有须要为iOS 4做适配的必要了)、
总之,聪明的开发人员总会尝试尽可能的自己主动化流程,已减轻自己的工作负担。而ARC恰恰就为我们提供了这种优点:自己主动帮我们完毕了非常多曾经须要手动完毕的工作,因此对我来说。转向ARC是一件不须要考虑的事情。
详细操作
说了这么多,最终能够实践一下了。在决定使用ARC后,非常多开发人员面临的首要问题是不知怎样下手。由于可能手上的项目已经用MRC写了一部分,不想麻烦做转变;或者由于新项目里用ARC时遇到了奇怪的问题,从而放弃ARC退回MRC。
这都是常见的问题。而在以下,将通过一个demo引导大家彻底转向ARC的世界。
Demo
样例非常easy,这是一个查找歌手的应用,包括一个简单的UITableView和一个搜索框。当用户在搜索框搜索时,调用MusicBrainz的API完毕名字搜索和匹配。MusicBrainz是一个开放的音乐信息平台。它提供了一个免费的XML网页服务。假设对MusicBrainz比較有兴趣的话。能够到它的官网逛一逛。
Demo的起始样例能够从这里下载。为了照应新人,在这边进行简单说明。
在Xcode中打开下载的样例。应该能够看到例如以下内容(Xcode和iOS开发熟练者请跳过此段)
AppDelegate.h/m 这是整个app的delegate。没什么特殊的,每一个iOS/Mac程序在main函数以后的入口,由此进入app的生命周期。在这里载入了最初的viewController并将其放到Window中展示出来。另外appDelegate还负责处理程序開始退出等系统托付的事件
MainViewController.h/m/xib 这个demo最基本的ViewController。含有一个TableView和一个搜索条。 SoundEffect.h/m 简单的播放声音的类。在MusicBrainz搜索完成时播放一个音效。
main.m 程序入口,全部c程序都从main函数開始运行
AFHTTPRequestOperation.h/m 这是有名的网络框架AFNetworking的一部分。用来帮助等简单地处理web服务请求。这里仅仅包括了这一个类而没有将所有的AFNetworking包括进来。由于我们仅仅用了这一个类。完整的框架代码能够在github的相关页面上找到https://github.com/gowalla/AFNetworking
SVProgresHUD.h/m/bundle 是一个经常使用的进度条指示,当搜索的时候出现以提示用户正在搜索请稍后。bundle是资源包,里面包括了几张该类用到的图片,打进bundle包的目的一方面是为了资源easy管理,还有一方面也是主要方面时为了不和其它资源发生冲突(Xcode中资源名字是资源的唯一标识,同名字的资源仅仅能出现一次。而放到bundle包里能够避免这个潜在的问题)。
SVProgresHUD能够在这里找到https://github.com/samvermette/SVProgressHUD
高速过一遍这个应用吧:MainViewController
是UIViewController
的子类,相应的xib文件定义了相应的UITableView
和UISearchBar
。
TableView中
显示searchResult
数组中的内容。当用户搜索时。用AFHTTPRequestOperation发一个HTTP请求。当从MusicBrainz得到回应后将结果放入searchResult
数组中并用tableView
显示,当返回结果是空时在tableView
中显示没找到。基本的逻辑都在MainViewController.m中的-searchBarSearchButtonClicked:
方法中,生成了用于查询的URL,依据MusicBrainz的需求替换了请求的header,而且完毕了返回逻辑。然后在主线程中刷新UI。整个程序还是比較简单的~
MRC到ARC的自己主动转换
回到正题,我们讨论的是ARC。关于REST API和XML解析的技术细节就临时先忽略吧..整个程序都是用MRC来进行内存管理的。首先来让我们把这个demo转成ARC吧。
基本上转换为ARC意味着把全部的retain
,release
和autorelease
keyword去掉,在之前我们明白几件事情:
- Xcode提供了一个ARC自己主动转换工具。能够帮助你将源代码转为ARC
- 当然你也能够自己动手完毕ARC转换
- 同一时候你也能够指定对于某些你不想转换的代码禁用ARC,这对于非常多庞大复杂的还没有转至ARC的第三方库帮助非常大。由于不是你写的代码你想动手改动的话代码超级easymess…
对于我们的demo。为了说明问题,这三种策略我们都将採用,注意这只不过为了展示怎样转换。
实际操作中不须要这么麻烦,并且今后的绝大部分情况应该是从project建立開始就是ARC的。
首先,ARC是LLVM3.0编译器的特性,而老的project特别是Xcode3时代的project的默认编译器非常可能是GCC或者LLVM-GCC。因此第一步就是确认编译器是否正确。在Project设置面板,选择target。在Build Settings中将Compiler for C/C++/Objective-C选为Apple LLVM compiler 3.0或以上。
为了确保之后转换的顺利,在这里我个人建议最好把Treat Warnings as Errors和 Run Static Analyzer都打开,确保在改变编译器后代码依然没有警告或者内存问题(尽管静态分析可能不太能保证这一点,可是聊胜于无)。好了~clean(Shift+Cmd+K
)以后Bulid一下试试看,经过改动后的demoproject没有不论什么警告和错误,这是非常好的開始。
(对于存在警告的代码,这里是非常好的修复的时机..请在转换前确保原来的代码没有内存问题)。
接下来就是完毕从MRC到ARC的伟大转换了。还是在Build Settings页面,把Objective-C Automatic Reference Counting改成YES(假设找不到的话请看一看搜索栏前面的小标签是不是调成All了..这个选项在Basic里是不出现的)。这样我们的project就将在全部源码中启用ARC了。
然后…试着编译一下看看,嗯..无数的错误。
这是非常正常的,由于ARC里不同意出现retain,release之类的,而MRC的代码这些是肯定会有的东西。
我们能够手动一个一个相应地去修复这些错误,可是这非常麻烦。Xcode为我们提供了一个自己主动转换工具。能够帮助重写源码。简单来说就是去掉多余的语句而且重写一些propertykeyword。
这个小工具是Edit->Refactor下的Convert to Objective-C ARC,点击后会让我们选择要转换哪几个文件,在这里为了说明除了自己主动转换外的方法,我们不所有转换,而仅仅是选取当中几个转换(MainViewController.m
和AFHTTPRequestOperation.m
不做转换,之后我们再手动将这两个转为ARC)。注意到这个对话框上有个警告标志告诉我们target已经是ARC了。这是因为之前我们在Build
Settings里已经设置了启用ARC。事实上直接在这里做转换后Xcode会自己主动帮我们开启ARC。点击检查后,Xcode告诉我们一个不幸的消息,不能转换,须要修复ARC readiness issues..后面还告诉我们要看到全部的所谓的ARC readiness issues。能够到设置的General里把Continue building after errors勾上…What the f**k…好吧~先乖乖听从Xcode的建议"Cmd+,“然后Continue building after errors打勾然后再build。
问题依然,只是在issue面板里应该能够看到全部出问题的代码了。在我们的样例里。问题出在SoundEffect.m里:
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
if (fileURL != nil) {
SystemSoundID theSoundID;
OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)fileURL, &theSoundID);
if (error == kAudioServicesNoError) {
soundID = theSoundID;
}
}
这里代码尝试把一个NSURL
指针强制转换为一个CFURLRef
指针。这里涉及到一些Core
Services特别是Core Foundation(CF)的东西,AudioServicesCreateSystemSoundID()函数接受CFURLRef为參数,这是一个CF的概念,可是我们在较高的抽象层级上所建立的是NSURL
对象。在Cocoa框架中,有非常多顶层对象对底层的抽象。而在使用中我们往往能够不加差别地对这两种对象进行相同的对待,这类对象即为能够"自由桥接"的对象(toll-free
bridged)。
NSURL和CFURLRef就是一对好基友好样例,在这里事实上CFURLRef
和NSURL
是能够进行替换的。
通常来说为了代码在底层级上的正确,在iOS开发中对基于C的API的调用所传入的參数一般都是CF对象,而Objective-C的API调用都是传入NSObject对象。因此在採用自由桥接来调用C API的时候就须要进行转换。可是在使用ARC编译的时候,由于内存管理的原因。编译器须要知道对这些桥接对象要实行什么样的操作。假设一个NSURL对象替代了CFURLRef。那么在作用区域外,应该由谁来决定内存释放和对象销毁呢?为了解决问题,引入了bridge,bridgetransfer和bridgeretained三个keyword。
关于选取哪个keyword做转换,须要由实际的代码行为来决定。假设对于自由桥接机制感兴趣,大家能够自己找找的相关内容。比方适用类型、内部机制和一个简单介绍~之后我也会对这个问题做进一步说明
回到demo,我们如今在上面的代码中(CFURLRef)前加上__bridge
进行转换。然后再执行ARC转换工具,这时候检查应该没有其它问题了,那么让我们进行转换吧~当然在真正转换之前会有一个预览界面,在这里我们最好检查一下转换是不是都依照预想进行了..要是出现大面积错误又没有备份或者出现各种意外的话就能够哭了…
前后变化的话比較简单。基本就是去掉不须要的代码和改变property的类型而已。事实上有信心的话不太须要每次都看,可是假设是第一次运行ARC转换的操作的话。我还是建议略微看一下变化,这样能对ARC有个直观上的了解。检查一遍,应该没什么问题了..须要注意的是main.m里关于autoreleasepool的变化以及全部dealloc调用里的[super dealloc]的删除,它们相同是MRC到ARC的主要变化..
好了~转换完毕以后我们再build看看..应该会有一些警告。
对于原来retain
的property,比較保险的做法是转为strong
,在LLVM3.0中自己主动转换是这样做的。可是在3.1中property默认并非strong
,这样在使用property赋值时存在警告。我们在property声明里加上strong
就好了~然后就是SVProgressHUD.m里可能存在问题。这是因为原作者把release
的代码和其它代码写在一行了.导致自己主动转换时仅仅删掉了部分,而留下了部分不应该存在的代码。删掉对变量的空的调用就好了..
自己主动转换之后的故事
然后再编译,没有不论什么错误和警告了,好棒~等等…我们刚才没有对MainViewController和AFHTTPRequestOperation进行处理吧,那么这两个文件中应该还存在release
之类的东西吧..?看一看这两个文件,果然有各种release
,可是为什么能编译通过呢?!明明刚才在自己主动转换前他们还有N多错的嘛…答案非常easy。在自己主动转换的时候由于我们没有勾选这两个文件,因此编译器在自己主动转换过后为这两个文件标记了"不使用ARC编译"。
能够看到在target的Building
Phases下,MainViewController.m和AFHTTPRequestOperation.m两个文件后面被加上了-fno-objc-arc
的编译标记。被加上该标记的文件将不使用ARC规则进行编译。(相对地,假设你想强制对某几个文件启用ARC的话,能够为其加上-fobjc-arc
标记)
提供这种编译标记的原因是显而易见的,由于总是有一部分的第三方代码并没有转换为ARC(可能是由于维护者犯懒或者已经终止维护),所以对于这部分代码。为了迅速完毕转换。最好是使用-fno-objc-arc标记来禁止在这些源代码上使用ARC。
为了方便查找。再此列出一些在转换时可能出现的问题,当然在我们使用ARC时也须要注意避免代码中出现这些问题:
-
“Cast … requires a bridged cast”
这是我们在demo中遇到的问题。不再赘述
-
Receiver type ‘X’ for instance message is a forward declaration
这往往是引用的问题。ARC要求完整的前向引用,也就是说在MRC时代可能仅仅须要在.h中申明@class就能够。可是在ARC中假设调用某个子类中未覆盖的父类中的方法的话。必须对父类.h引用,否则无法编译。
-
Switch case is in protected scope
如今switch语句必须加上{}了,ARC须要知道局部变量的作用域,加上{}后switch语法更加严格,否则遇到没有break的分支的话内存管理会出现故障。
-
A name is referenced outside the NSAutoreleasePool scope that it was declared in...
这是因为写了自己的autoreleasepool,而在转换时在原来的pool中申明的变量在新的@autoreleasepool中作用域将被局限。解决方法是把变量申明拿到pool的申请之前。
-
ARC forbids Objective-C objects in structs or unions
能够说ARC所引入的最严格的限制是不能在C结构体中放OC对象了..因此类似以下这种代码是不可用的
typedef struct {
UIImage *selectedImage;
UIImage *disabledImage;
} ButtonImages;
这个问题仅仅有乖乖想办法了..改变原来的结构什么的..
手动转换
刚才做了对demo的大部分转换。还剩下了MainViewController和AFHTTPRequestOperation是MRC。可是因为使用了-fno-objc-arc
,因此如今编译和执行都没有问题了。以下我们看看怎样手动把MainViewController转为ARC,这也有助于进一步理解ARC的规则。
首先,我们须要转变一下观念…对于MainViewController.h,在.h中申明了两个实例变量:
@interface MainViewController : UIViewController
{
NSOperationQueue *queue;
NSMutableString *currentStringValue;
}
我们最好还是细致考虑一下,为什么在interface里出现了实例变量的申明?通常来说,实例变量仅仅是在类的实例中被使用,而你所写的类的使用者并没有太多必要了解你的类中有哪些实例变量。而对于绝大部分的实例变量,应该都是protected
或者private
的,对它们的操作仅仅应该用setter
和getter
。而这正是property所要做的工作。能够说。将实例变量写在头文件里是一种遗留的陋习。更好的写实例变量名字的地方应当与类实现关系更为密切,为了隐藏细节,我们应该考虑将它们写在@implementation里。
好消息是。在LLVM3.0中。不论是否开启ARC,编译器是支持将实例变量写到实现文件里的。甚至假设没有特殊须要又用了property,我们都不应该写无意义的实例变量申明,由于在@synthesize中进行绑定时,我们就能够设置变量名字了。这样写的话能够让代码更加简洁。
在这里我们对着两个实例变量不须要property(外部成员不应当能訪问到它们),因此我们把申明移到.m里中。改动后的.h是这种,十分简洁一看就懂~
#import
@interface MainViewController : UIViewController
@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
@end
然后.m的开头变成这样:
@implementation MainViewController
{
NSOperationQueue *queue;
NSMutableString *currentStringValue;
}
这种写法让代码相当灵活。并且不得不承认.m确实是这些实例变量的应该在的地方…build一下,没问题..当然对于SoundEffect类也能够做相似的操作,这会让使用你的类的人非常开心,由于.h越简单越好..P.S.另外一个优点能够降低.h里的引用。降低编译时间(尽管不明显=。=)
然后就能够在MainViewController里启用ARC了,方法非常easy。删掉Build Phases里相关文件的-fno-objc-arc标记就能够了~然后..然后当然是一大堆错误啦。我们来手动一个个改吧,尽管谈不上乐趣,可是成功以后也会非常有成就~(假设你不幸在启用ARC后build还是成功了,恭喜你遇到了Xcode的bug。请Cmd+Q然后又一次打开Xcode把=_=)
dealloc
红色最密集的地方是dealloc
,由于每一行都是release
。由于在这里dealloc
并没有做除了release
和super
dealloc
之外的不论什么事情,因此简单地把整个方法删掉就好了。
当然。在对象被销毁时。dealloc
还是会被调用的,因此我们在须要对非ARC管理的内存进行管理和必要的逻辑操作的时候,还是应该保留dealloc
的,当然这涉及到CF以及下面层的东西:比方对于retain
的CF对象要CFRelease()
,对于malloc()
到堆上的东西要free()
掉,对于加入的observer
能够在这里remove。schedule的timer在这里invalidate
等等~[super
dealloc]
这个消息也不再须要发了,ARC会自己主动帮你搞定。
另外,在MRC时代一个常做的事情是在dealloc
里把指向自己的delegate设成nil(否则就等着EXCBADACCESS吧
:P),而如今一般delegate都是weak
的。因此在self被销毁后这个指针自己主动被置成nil
了,你不用再为之操心。好棒啊..
去掉各种release和autorelease
这个非常直接。没有不论什么问题。去掉即可了~不再多说
讨论一下Property
在MainViewController.m里的类扩展中定义了两个property:
@interface MainViewController ()
@property (nonatomic, retain) NSMutableArray *searchResults;
@property (nonatomic, retain) SoundEffect *soundEffect;
@end
申明的类型是retain
。关于retain
,assign
和copy
的讨论已经烂大街了,在此不再讨论。在MRC的年代使用property能够帮助我们使用dot
notation的时候简化对象的retain
和copy
。而在ARC时代,这就显得比較多余了。在我看来,使用property和点方法来调用setter和getter是不必要的。
property仅仅在将须要的数据在.h中暴露给其它类时才须要,而在本类中,仅仅须要用实例变量就能够。(更新,如今笔者在这点上已经不纠结了,任意就好。自己明确即可。可是或许还是用点方法会好一些。至少能够分清楚究竟是操作了实例变量还是调用了setter和getter)。因此我们能够移去searchResults和soundEffect的@property和@synthesize。并将起移到实例变量申明中:
#import "plementation MainViewController
{
NSOperationQueue *queue;
NSMutableString *currentStringValue;
NSMutableArray *searchResults;
SoundEffect *soundEffect;
}
相应地,我们须要将相应的self.searchResult
和self.soundEffect
的self.都去去掉。
在这里须要注意的是。尽管我们去掉了soundEffect的property和synthesize,可是我们依旧有一个lazy
loading的方法-(SoundEffect
*)soundEffect
,奇妙之处在于(可能你曾经也不知道)。点方法并不须要@propertykeyword的支持,尽管大部分时间是这么用的..(property仅仅是对setter或者getter的申明,而点方法是对其的调用,在这个样例的实现中我们其实实现了-soundEffect这个getter方法。所以点方法在等号右边的getter调用是没有问题的)。为了避免误解,建议把self.soundEffect的getter调用改写成[self soundEffect]。
然后我们看看.h里的property~里面有两个retain
的IBOutlet。retain
keyword在ARC中是依然可用的。它在ARC中所扮演的角色和strong
全然一样。为了避免迷惑,最好在须要的时候将其写为strong,那样更符合ARC的规则。对于这两个property,我们将其申明为weak
(其实,假设没有特别意外,除了最顶层的IBOutlet意外,自己写的outlet都应该是weak
)。通过载入xib得到的用户界面,在其从xib文件载入时,就已经是view
hierarchy的一部分了,而view hierarchy中的指向都是strong的。
因此outlet所指向的UI对象不应当再被hold一次了。将这些outlet写为weak的最显而易见的优点是你就不用再viewDidUnload方法中再将这些outlet设为nil了(否则就算view被摧毁了,可是因为这些UI对象还在被outlet指针指向而无法释放。代码简洁了非常多啊..)。
在我们的demo中将IBOutlet的property改为weak
而且删掉viewDidUnload中关于这两个IBOutlet的内容~
总结一下新增加的property的keyword类型:
- strong 和原来的retain比較相似。strong的property将相应__strong的指针。它将持有所指向的对象
- weak 不持有所指向的对象。并且当所指对象销毁时能将自己置为nil。基本全部的outlet都应该用weak
-
unsafe_unretained 这就是原来的assign。
当须要支持iOS4时须要用到这个keyword
- copy 和原来基本一样..copy一个对象而且为其创建一个strong指针
-
assign 对于对象来说应该永远不用assign了,实在须要的话应该用unsafe_unretained取代(基本找不到这种时候。大部分assign应该都被weak替代)。
可是对于基本类型比方int,float,BOOL这种东西,还是要用assign。
特别地,对于NSString
对象,在MRC时代非常多人喜欢用copy。而ARC时代一般喜欢用strong…(我也不懂为什么..求不吝赐教)
自由桥接的细节
MainViewController如今剩下的问题都是桥接转换问题了~有关桥接的部分有三处:
- (NSString *)CFURLCreateStringByAddingPercentEscapes(…):CFStringRef至NSString *
- (CFStringRef)text:NSString *至CFStringRef
- (CFStringRef)@“!_‘();:@&=+$,/?%#[]":NSString _至CFStringRef
编译器对前两个进行了报错,最后一个是常量转换不涉及内存管理。
关于toll-free bridged,假设不进行细究。NSString
和CFStringRef
是一样的东西。新建一个CFStringRef
能够这么做:
CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!",name];
然后,这里alloc
了而s1是一个CF指针,要释放的话,须要这样:
CFRelease(s1);
相似地能够用CFStringRef
来转成一个NSString
对象(MRC):
CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault,bytes, kCFStringEncodingMacRoman);
NSString *s3 = (NSString *)s2;
// release the object when you're done
[s3 release];
在ARC中。编译器须要知道这些指针应该由谁来负责释放,假设把一个NSObject
看做是CF对象的话,那么ARC就不再负责它的释放工作(记住ARC是only
for NSObject的)。对于不须要改变持有者的对象。直接用简单的bridge就能够了,比方之前在SoundEffect.m做的转换。
在这里对于(CFStringRef)text这个转换,ARC已经负责了text这个NSObject的内存管理,因此这里我们须要一个简单的bridge。
而对于CFURLCreateStringByAddingPercentEscapes
方法。方法中的create暗示了这种方法将形成一个新的对象,假设我们不须要NSString
转换,那么为了避免内存的问题,我们须要使用CFRelease
来释放它。而这里我们须要一个NSString
。因此我们须要告诉编译器接手它的内存管理工作。
这里我们使用bridge_transferkeyword。将内存管理权由CF object移交给NSObject(或者说ARC)。
假设这里我们仅仅用bridge的话,内存管理的负责人没有改变。那么这里就会出现一个内存泄露。
另外有时候会看到CFBridgingRelease()
。这事实上就是transfer
cast的内联写法..是一样的东西。总之。须要记住的原则是,当在涉及CF层的东西时,假设函数名中有含有Create, Copy, 或者Retain之中的一个。就表示返回的对象的retainCount+1了。对于这种对象,最安全的做法是将其放在CFBridgingRelease()
里,来平衡retain
和release
。
另一种bridge方式,__bridge_retained
。顾名思义。这样的转换将在转换时将retainCount加1。和CFBridgingRelease()
相似。也有一个内联方法CFBridgingRetain()
来负责和CFRelease()
进行平衡。
须要注意的是,并不是全部的CF对象都是自由桥接的,比方Core Graphics中的全部对象都不是自由桥接的(如CGImage
和UIImage
,CGColor
和UIColor
)。另外也不是仅仅有自由桥接对象才干用bridge来桥接。一个非常好的特例是void
_(指向随意对象的指针,类似id),对于void _和随意对象的转换,一般使用_bridge
。
(这在将ARC运用在Cocos2D中非常实用)
最终搞定了
至此整个project都ARC了~对于AFHTTPRequestOperation这种不支持ARC的第三方代码,我们的选择一般都是就不使用ARC了(或者等开源社区的大大们更新ARC适配版本号)。能够预见,在最近会有越来越多的代码转向ARC,可是也一定会有大量的代码临时或者永远保持MRC等个,所以对于这些代码就不用太纠结了~