在ARC以前,iOS内存管理不管对资深级仍是菜鸟级开发者来讲都是一件很头疼的事。我参 加过几个使用手动内存管理的项目,印象最深入的是一个地图类应用,因为应用自己就很是耗内存,当时为了解决内存泄露问题,每周都安排有人值班用 Instruments挨个跑功能,关键是每次都总能检查出来很多。其实不论是菜鸟级仍是资深级开发者都避免不了写出内存泄露的代码,规则你们都懂,但是 天知道何时手一抖就少写了个release?html
好在项目决定转成ARC了,下面将本身转换的过程和中间遇到的问题写出来和你们共享,但愿能减小你们解决同类问题的时间。ios
1、前言objective-c
项目简介app
须要转换的Objective-C文件数量:1000个左右。工具
开发工具:Xcode 6.0.1开发工具
转换方式网站
我使用的是Xcode自己提供的ARC转换功能。固然你也能够手动手动转换,那不属于本文范畴,并且其工做量绝对能让你崩溃。ui
2、转换过程this
代码备份spa
在进行如此大规模的更改以前,必定要先进行代码备份:直接在本地将代码复制一份,或者记住更改前代码在VCS上的版本号。
过滤无需转换的文件
找出项目中引用的仍使用手动内存管理的第三方库,或者某些你不但愿转换的文件,对其添加-fno-objc-arc标记。
Xcode自动转换工具只针对Objective-C对象,只会处理Objective-C/Objective-C++即后缀名为.m/.mm的两种文件,所以其余的C/C++对应的.c/.cpp都无需理会。
执行检查操做
使用Xcode转换工具入口如图所示:
点击Convert to Objective-C ARC后会进入检查操做入口,如图:
该步骤要选择哪些文件须要转换,若是前面将无需转换的文件都添加了-fno-objc-arc标记后,这里能够全选。
点击check按钮后Xcode会帮助咱们检查代码中存在的不符合ARC使用规则的错误或警告,只有全部的错误都解决之后才能执行真正的转换操做。
解决错误/告警
执行完check操做后,会给出提示:
三百多个错误,同时还有一千两百多个警告信息,都要哭了。。。
错误和警告的解决内容较多,后面会单独介绍。
执行转换操做
解决完全部的error后,会弹出下述提示界面:
大意是Xcode将要将你的工程转换成使用ARC管理内存,全部更改的代码在真正更改以前 会在一个review界面展现。同时全部的更改完成之后,Xcode会讲项目Target对应的工程设置的使用ARC设置(Objective-C Automatic Reference Counting)会被置成YES(上图右上角的警告标识就是在告诉咱们项目已经支持ARC了,但工程中有文件还不支持):
这时离成功就不远了,胜利在望!
点击next按钮后跳转到review界面,样式相似于使用Xcode提交SVN的确认提交界面,以下图所示:
该界面列出了全部须要有代码更改的文件,同时可以直接对比转换前和转换后的代码变化。为了稳妥起见,我选择了每一个文件都点进去扫了一眼,这也给咱们一次机会检查是否漏掉了不能转换的文件。肯定一切无误之后,点击右下角的save按钮,一切就大功告成了!
错误/警告解决
错误
ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute
property属性必须指定一个内存管理关键字,在属性定义处增长strong关键字便可。
ARC forbids explicit message send of ‘release’
这种状况一般是使用包含release的宏定义,将该宏和使用该宏的地方删除便可。
Init methods must return a type related to the receiver type
错误缘由是A类里的一个方法以init开头,并且返回的是B类型,好吧,乖乖改方法名。
Cast of C pointer type ‘ivPointer’ (aka ‘void ’) to Objective-C pointer type ‘iFlyTTSManager_old ’ requires a bridged cast
cast_pointer_objective-c
这是Toll-Free Bridging转换问题,在ARC下根据状况使用对应的转换关键字就好了,后文会专门介绍。
警告
解决警告的目的是消除警告处代码存在的隐患,既然Xcode给了提示,那么每个警告信息都值得咱们认真对待。
Capturing self in this block is likely to lead to a retain cycle
这是典型的block循环引用问题,将block中的self改为使用指向self的weak指针便可。
Using ‘initWithArray:’ with a literal is redundant
好吧,原来是不必的alloc操做,直接按Xcode提示将alloc删除便可:
Init methods must return a type related to the receiver type
原来是A类里的一个方法以init开头,并且返回的是B类型,好吧,乖乖改方法名。
Property follows Cocoa naming convention for returning ‘owned’ objects
这是由于@property属性的命名以new开头了,可恶。。。修改方法是将对应的getter方法改为非new开头命名的:
ARC下方法名若是是以new/alloc/init等开头的,并且还不是类的初始化方法,就该当心了,要么报错,要么警告,缘由你懂的。
Block implicitly retains ‘self’; explicitly mention ‘self’ to indicate this is intended behavior
意思是block中使用了self的实例变量 _selectedModeMarkerView,所以block会隐式的retain住self。Xcode认为这可能会给开发者形成困惑,或者所以而 因袭循环引用,因此警告咱们要显示的在block中使用self,以达到block显示retain住self的目的。
该警告有两种改法: ①按照Xcode提示,改为self->_selectedModeMarkerView:
②直接将该警告关闭 警告名称为:Implicit retain of ‘self’ within blocks 对应的Clang关键字是:-Wimplicit-retain-self
Weak property may be unpredictably set to nil 和 Weak property ‘delegate’ is accessed multiple times in this method but may be unpredictably set to nil; assign to a strong variable to keep the object alive
这是工程中数目最多的警告,这是由于全部的delegate属性都是weak的,Xcode默认开启了下图中的两个警告设置,将其关闭便可:
Capturing ‘self’ strongly in this block is likely to lead to a retain cycle
这是明显的block致使循环引用内存泄露的状况,以前代码中坑啊!修改方案:
Method parameter of type ‘NSError __autoreleasing ’ with no explicit ownership
这种就不用说了,按警告中的提示添加__autoreleasing关键字便可。
以上列出的错误和警告只是数量较多的,还有不少其余就不在这里一一列举了。
另外,推荐 Mattt Thompson 大神关于Clang中几乎全部warning的名称和对应报错提示语的网站:http://fuckingclangwarnings.com/,之后解决warning类问题就简单多了!
Xcode自动转换
关键字转换
Xcode会自动将某些关键字自动转换成ARC的对应版本。
retain自动转成strong,如图:
assign关键字转成weak
修饰Objective-C对象或者id类型对象的assign关键字会被转成weak,如图:
可是修饰Int/bool等数值型变量的assign不会自动转换成weak,如图:
关键字删除
和手动内存管理相关的几个关键字,好比:release/retain/autorelease/super dealloc等会被删除;
dealloc方法中若是除了release/super dealloc语句外,若是别的代码,dealloc方法会保留,如图:
若是没有整个方法都会被删除:
关键字替换
在转换时block关键字会被自动替换成weak:
@autoreleasepool
NSAutoreleasePool不支持ARC,会被替换成@autoreleasepool:
关于被宏注释代码
使用宏定义的对象释放代码
宏定义以下所示:
1
2
|
#define RELEASE_SAFELY(__POINTER) { \
[(__POINTER) release]; (__POINTER) = nil; }
|
在执行ARC转换检查操做时,Xcode会在使用该宏的地方报错:
将该宏和使用该宏的地方删除便可。
被宏注释掉的代码,Xcode在转换时是不会处理的,如图:
PS:这是至关坑的一点,由于你根本预料不到工程中使用了多少宏,注释掉了多少代码。当你执行完转换操做,觉得就大功告成的时候,却在某天由于一个宏的开启遇到了一堆新的转ARC不完全的问题。这种问题也没招,只能遇到一个改一个了。
ARC和block
不论是手动内存管理仍是ARC,block循环引用致使的内存泄露都是一个使人头疼的问题。在MRC中,解决block循环引用只须要使用__block关键字,在ARC下解决与block的使用就略显复杂了:
__block关键字
block内修改外部定义变量
和手动内存管理同样,ARC若是在block中须要修改block以外定义的变量须要使用__block关键字修饰,好比:
1
2
3
4
|
__block NSString *name = @
"foggry"
;
self.expireCostLabel.completionBlock = ^(){
name = @
"wangzz"
;
};
|
上例中name变量须要在block中修改,所以必须使用__block关键字。
__block在MRC和ARC中的区别
在ARC下的block中使用__block关键字修饰的对象时,block会retain该对象;而在MRC下却不会retain。关于这点在官方文档Transitioning to ARC Release Notes中有详细的描述:
In manual reference counting mode, block id x; has the effect of not retaining x. In ARC mode, block id x; defaults to retaining x (just like all other values).
下面的代码无论在MRC仍是ARC中myController对象都是有内存泄露的:
1
2
3
4
5
|
MyViewController *myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
};
|
内存泄露问题在MRC中能够按以下方式更改:
1
2
3
4
5
|
MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
};
|
然而在ARC中这么改就不行了。正如开始所说的那样,在ARC中myController.completionHandler的block会retainmyController对象,使得内存泄露问题仍然存在!!
在ARC中该问题有两种解决方案,第一种:
1
2
3
4
5
6
|
MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
myController = nil;
};
|
该方法在block中使用完myController时,是它指向nil。没有strong类型的指针指向myController指向的对象时,对象会被释放掉。
第二种种解决方案,直接使用weak代替block关键字:
1
2
3
4
5
6
|
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler = ^(NSInteger result) {
[weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};
|
该方法直接避免了对block对myController对象的retain。
存在循环引用关系
若是self直接或者间接的对block存在强引用,在block中又须要使用self关键字,此时self和block就存在循环引用的关系。此时必须使用__weak关键字定义一个指针指向self,在block中使用该指针来引用self:
1
2
3
4
|
MessageListController * __weak weakSelf = self;
self.messageLogic.loadMoreBlock = ^(IcarMessage * theMessage) {
[weakSelf.tableView setPullTableIsLoadingMore:YES];
};
|
须要说明的是,尽管上例中weakSelf指针对self只是弱引用,可是self对block倒是强引用,self的生命周期必定是长于block的,所以不用担忧在block中使用weakSelf指针时,其指向的self会被释放掉。
不存在循环引用关系
下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if
(strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else
{
// Probably nothing...
}
};
|
如前面所说,myController.completionHandler的block中不能直接使用myController对象,会形成内存泄露, 所以须要先用一个weak的指针指向myController对象,而后在block中使用该weak指针。可是为了确保在block执行的时候 myController对象没有被释放掉,就在block一开始的地方定义了一个临时的strong类型的指针strongMyController指 向weak指针weakMyController,其实最终的结果就是block中对myController对象强引用了。在block执行完被销毁的 时候,strongMyController指针变量会被销毁,其最终指向的myController对象所以也会被销毁。这样在使用一个对象的时候作就 保证了该对象是存在的,使用完了再放弃该对象的全部权。
ARC和Toll-Free Bridging
MRC下的Toll-FreeBridging不涉及内存管理的转移,Objective-C(后文简称OC)和Core Foundation(后文简称CF)各自管理各自的内存,相互之间能够直接交换使用,好比:
1
2
|
NSLocale *gbNSLocale = [[NSLocale alloc] initWithLocaleIdentifier:@
"en_GB"
];
CFLocaleRef gbCFLocale = (CFLocaleRef)gbNSLocale;
|
而在ARC下,事情就会变得复杂一些。由于ARC可以管理OC对象的内存,却不能管理CF对象,CF对象依然须要咱们手动管理内存。在CF和OC之间 bridge对象的时候,问题就出现了,编译器不知道该如何处理这个同时有OC指针和CF指针指向的对象。这时候,须要使用__bridge, __bridge_retained, __bridge_transfer等修饰符来告诉编译器该如何去作。
__bridge
它告诉编译器仍然负责管理好在OC一端的引用计数的事情,开发者也继续负责管理好在CF一端的事情,好比:
1
2
3
4
|
CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault,
"CFString"
, kCFStringEncodingUTF8);
NSString *ocString = (__bridge NSString *)cfString;
CFRelease(cfString);
NSLog(@
"%@"
,ocString);
|
__bridge_retained 或 CFBridgingRetain
两者做用是同样的,只是用法不一样。
告诉编译器须要retain对象,而开发者在CF一端负责释放。这样就算对象在OC一端被释放,只要开发者不释放CF一端的对象, 对象就不会被真的销毁。
1
2
3
4
5
6
|
NSArray *ocArray = [[NSArray alloc] initWithObjects:@
"foggry"
, nil];
CFArrayRef cfArray = (__bridge_retained CFArrayRef)ocArray;
/**
使用cfArray
**/
CFRelease(cfArray);
|
__bridge_transfer 或 CFBridgingRelease
两者做用也是同样的,只是用法不一样。
该关键字告诉编译器bridge的同时,也转移了对象的全部权,好比:
1
2
3
4
|
CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault,
"CFString"
, kCFStringEncodingUTF8);
NSString *ocString = (__bridge_transfer NSString *)cfString;
//CFRelease(cfString); //再也不须要释放操做
NSLog(@
"%@"
,ocString);
|
转换过程当中你们只须要根据具体需求选用适当的关键字便可。
另外,在ARC中id和void *也不能直接相互转换了,必须经过Toll-FreeBridging使用适当的关键字修饰。
ARC和IBOutLet
对于IBOutLet属性应该用strong仍是weak一直都有疑惑。关于这一点官方文档是这么介绍的:
From a practical perspective, in iOS and OS X outlets should be defined as declared properties. Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib >>>file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create should therefore typically be weak.
那么长的一段英文想说的是:若是nib文件构建的view是直接被Controller引用的顶层view,对应的IBOutLet属性应该是strong;
若是view是顶层view上的一个子view,那么该view的属性应该是weak,由于顶层view被Controller使用strong属性引用了,而顶层view自己又持有该view;
若是Controller对某个view须要单独引用,或者Controller没有引用某个view的父view,那么其属性也应该是strong。
好吧,其实我能说若是你实在懒得区分何时用strong,何时用weak,那就将因此后的IBOutLet属性都设成strong吧!在 Controller销毁的时候,对应的IBOutLet实例变量也会被销毁,strong指针会被置成nil,所以也不会有内存问题。
参考文档
Transitioning to ARC Release Notes