永发信息网

如何mrc工程中兼容编译arc文件

答案:1  悬赏:40  手机版
解决时间 2021-04-01 08:52
如何mrc工程中兼容编译arc文件
最佳答案
例子很简单,这是一个查找歌手的应用,包含一个简单的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的相关页面上找
SVProgresHUD.h/m/bundle 是一个常用的进度条指示,当搜索的时候出现以提示用户正在搜索请稍后。bundle是资源包,里面包含了几张该类用到的图片,打进bundle包的目的一方面是为了资源容易管理,另一方面也是主要方面时为了不和其他资源发生冲突(Xcode中资源名字是资源的唯一标识,同名字的资源只能出现一次,而放到bundle包里可以避免这个潜在的问题)。SVProgresHUD可以在这里找到
快速过一遍这个应用吧: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关键字去掉,在之前我们明确几件事情:
* Xcode提供了一个ARC自动转换工具,可以帮助你将源码转为ARC
* 当然你也可以自己动手完成ARC转换
* 同时你也可以指定对于某些你不想转换的代码禁用ARC,这对于很多庞大复杂的还没有转至ARC的第三方库帮助很大,因为不是你写的代码你想动手修改的话代码超级容易mess…
对于我们的demo,为了说明问题,这三种策略我们都将采用,注意这仅仅只是为了展示如何转换。实际操作中不需要这么麻烦,而且今后的绝大部分情况应该是从工程建立开始就是ARC的。

首先,ARC是LLVM3.0编译器的特性,而老的工程特别是Xcode3时代的工程的默认编译器很可能是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一下试试看,经过修改后的demo工程没有任何警告和错误,这是很好的开始。(对于存在警告的代码,这里是很好的修复的时机..请在转换前确保原来的代码没有内存问题)。

接下来就是完成从MRC到ARC的伟大转换了。还是在Build Settings页面,把Objective-C Automatic Reference Counting改成YES(如果找不到的话请看一看搜索栏前面的小标签是不是调成All了..这个选项在Basic里是不出现的),这样我们的工程就将在所有源代码中启用ARC了。然后…试着编译一下看看,嗯..无数的错误。

这是很正常的,因为ARC里不允许出现retain,release之类的,而MRC的代码这些是肯定会有的东西。我们可以手动一个一个对应地去修复这些错误,但是这很麻烦。Xcode为我们提供了一个自动转换工具,可以帮助重写源代码,简单来说就是去掉多余的语句并且重写一些property关键字。

这个小工具是Edit->Refactor下的Convert to Objective-C ARC,点击后会让我们选择要转换哪几个文件,在这里为了说明除了自动转换外的方法,我们不全部转换,而只是选取其中几个转换(MainViewController.m和AFHTTPRequestOperation.m不做转换)。注意到这个对话框上有个警告标志告诉我们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,bridge_transfer和__bridge_retained三个关键字。关于选取哪个关键字做转换,需要由实际的代码行为来决定。如果对于自由桥接机制感兴趣,大家可以自己找找的相关内容,比如适用类型、内部机制和一个简介~之后我也会对这个问题做进一步说明
回到demo,我们现在在上面的代码中加上__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多错的嘛…答案很简单,在自动转换的时候因为我们没有勾选这两个文件,因此编译器在自动转换过后为这两个文件标记了”不使用ARC编译”。可以看到在target的Building Phases下,MainViewController.m和AFHTTPRequestOperation.m两个文件后面被加上了-fno-objc-arc的编译标记,被加上该标记的文件将不使用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了,方法很简单,删掉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(否则就等着EXC_BAD_ACCESS吧 ),而现在一般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中暴露给其他类时才需要,而在本类中,只需要用实例变量就可以。因此我们可以移去searchResults和soundEffect的@property和@synthesize,并将起移到实例变量申明中:
@implementation MainViewController
{
NSOperationQueue *queue;
NSMutableString *currentStringValue;
NSMutableArray *searchResults;
SoundEffect *soundEffect;
}

相应地,我们需要将对应的self.searchResult和self.soundEffect的self.都去去掉。在这里需要注意的是,虽然我们去掉了soundEffect的property和synthesize,但是我们依然有一个lazy loading的方法- (SoundEffect *)soundEffect,神奇之处在于(可能你以前也不知道),点方法并不需要@property关键字的支持,虽然大部分时间是这么用的..(property只是对setter或者getter的申明,而点方法是对其的调用,在这个例子的实现中我们事实上实现了-soundEffect这个getter方法,所以点方法在等号右边的getter调用是没有问题的)。为了避免误解,建议把self.soundEffect的getter调用改写成[self soundEffect]。
我要举报
如以上问答信息为低俗、色情、不良、暴力、侵权、涉及违法等信息,可以点下面链接进行举报!
大家都在看
求爱奇艺vip会员'看电影'
我在淘宝上拍的老上海7120机芯 19钻的 我花16
兄弟彩印怎么这么臭啊
3.45÷0.99竖式计算,商的近似数。
井下水如何治理?
如何硬件采集截取USB并口串口网口小票打印机
<头悬梁,锥刺股>打一成语
影响深沟球轴承的寿命因素有哪些
喝仙草鲜奶茶是不是睡不着觉
刚买下华为荣耀6x卡脱怎么取不出来
不言者多时,不笑者竟日是什么意思
苹果6老是显示两个信号怎么回事?
白麒麟开卡45天内20倍积分活动已经停止了么
9()9()9=6只能添 +-x÷
通过手机app,分析他的人?
推荐资讯
公众章节抄袭侵权吗?
假设你叫李华。你的表哥王平在美国留学,今年
丽台k2200显卡驱动问题,主要软件solidworks2
萧山融创东南海现在房价
大话职业,小妖种竹子得多少个小时
哪个网购可以微信支付
有没有必要买个哑铃凳
武汉和长沙那个发达
男生穿校服配什么鞋子好,越简单越好
-----Are you pleased with the play? ------
邻居家养鸽子对我家有什么风水说法吗
把门牙打掉算轻伤吗
正方形一边上任一点到这个正方形两条对角线的
阴历怎么看 ?