zoukankan      html  css  js  c++  java
  • iOS之mrc转arc

    一、前言

    项目简介

    需要转换的Objective-C文件数量:1000个左右。

    开发工具:Xcode 8.0.1

    转换方式

    我使用的是Xcode本身提供的ARC转换功能。当然你也可以手动手动转换,那不属于本文范畴,而且其工作量绝对能让你崩溃。

    二、转换过程

    代码备份

    在进行如此大规模的更改之前,一定要先进行代码备份:直接在本地将代码复制一份,或者记住更改前代码在VCS上的版本号。

    过滤无需转换的文件

    找出项目中引用的仍使用手动内存管理的第三方库,或者某些你不希望转换的文件,对其添加-fno-objc-arc标记。

    Xcode自动转换工具只针对Objective-C对象,只会处理Objective-C/Objective-C++即后缀名为.m/.mm的两种文件,因此其他的C/C++对应的.c/.cpp都无需理会。

    执行检查操作

    使用Xcode转换工具入口如图所示:

     点击Convert to Objective-C ARC后会进入检查操作入口,如图:

    该步骤要选择哪些文件需要转换,Xcode会自动帮你识别哪些文件需要转换,这里可以全选。

    点击check按钮后Xcode会帮助我们检查代码中存在的不符合ARC使用规则的错误或警告,只有所有的错误都解决以后才能执行真正的转换操作。

    解决错误/告警

    执行完check操作后,会给出提示:

    error.png

    三百多个错误,同时还有一千两百多个警告信息,都要哭了。。。

    错误和警告的解决内容较多,后面会单独介绍。

    执行转换操作

    解决完所有的error后,会弹出下述提示界面:

    t20_18_link_binary1.png

    大意是Xcode将要将你的工程转换成使用ARC管理内存,所有更改的代码在真正更改之前会在一个review界面展示。同时所有的更改完成以后,Xcode会讲项目Target对应的工程设置的使用ARC设置(Objective-C Automatic Reference Counting)会被置成YES(上图右上角的警告标识就是在告诉我们项目已经支持ARC了,但工程中有文件还不支持):

    069.png

    这时离成功就不远了,胜利在望!

    点击next按钮后跳转到review界面,样式类似于使用Xcode提交SVN的确认提交界面,如下图所示:

    070.png

    该界面列出了所有需要有代码更改的文件,同时能够直接对比转换前和转换后的代码变化。为了稳妥起见,我选择了每个文件都点进去扫了一眼,这也给我们一次机会检查是否漏掉了不能转换的文件。确定一切无误以后,点击右下角的save按钮,一切就大功告成了!

    错误/警告解决

    错误

    ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute

    071.png

    property属性必须指定一个内存管理关键字,在属性定义处增加strong关键字即可。

    ARC forbids explicit message send of ‘release’

    072.png

    这种情况通常是使用包含release的宏定义,将该宏和使用该宏的地方删除即可。

    Init methods must return a type related to the receiver type

    073.png

    错误原因是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

    074.png

    这是Toll-Free Bridging转换问题,在ARC下根据情况使用对应的转换关键字就行了,后文会专门介绍。

    警告

    解决警告的目的是消除警告处代码存在的隐患,既然Xcode给了提示,那么每一个警告信息都值得我们认真对待。

    Capturing self in this block is likely to lead to a retain cycle

    075.png

    这是典型的block循环引用问题,将block中的self改成使用指向self的weak指针即可。

    Using ‘initWithArray:’ with a literal is redundant

    076.png

    好吧,原来是没必要的alloc操作,直接按Xcode提示将alloc删除即可:

    077.png

    Init methods must return a type related to the receiver type

    078.png

    原来是A类里的一个方法以init开头,而且返回的是B类型,好吧,乖乖改方法名。

    Property follows Cocoa naming convention for returning ‘owned’ objects

    079.png

    这是因为@property属性的命名以new开头了,可恶。。。修改方法是将对应的getter方法改成非new开头命名的:

    080.png

    ARC下方法名如果是以new/alloc/init等开头的,而且还不是类的初始化方法,就该小心了,要么报错,要么警告,原因你懂的。

    Block implicitly retains ‘self’; explicitly mention ‘self’ to indicate this is intended behavior

    081.png

    意思是block中使用了self的实例变量_selectedModeMarkerView,因此block会隐式的retain住self。Xcode认为这可能会给开发者造成困惑,或者因此而因袭循环引用,所以警告我们要显示的在block中使用self,以达到block显示retain住self的目的。

    该警告有两种改法: ①按照Xcode提示,改成self->_selectedModeMarkerView:

    082.png

    ②直接将该警告关闭 警告名称为:Implicit retain of ‘self’ within blocks 对应的Clang关键字是:-Wimplicit-retain-self

    083.png

    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

    084.png

    这是工程中数目最多的警告,这是因为所有的delegate属性都是weak的,Xcode默认开启了下图中的两个警告设置,将其关闭即可:

    085.png

    Capturing ‘self’ strongly in this block is likely to lead to a retain cycle

    086.png

    这是明显的block导致循环引用内存泄露的情况,之前代码中坑啊!修改方案:

    087.png

    Method parameter of type ‘NSError __autoreleasing ’ with no explicit ownership

    088.png

    这种就不用说了,按警告中的提示添加__autoreleasing关键字即可。

    以上列出的错误和警告只是数量较多的,还有很多其他就不在这里一一列举了。

    另外,推荐  Mattt Thompson 大神关于Clang中几乎所有warning的名称和对应报错提示语的网站:http://fuckingclangwarnings.com/,以后解决warning类问题就简单多了!

    Xcode自动转换

    关键字转换

    Xcode会自动将某些关键字自动转换成ARC的对应版本。

    retain自动转成strong,如图:

    089.png

    assign关键字转成weak

    修饰Objective-C对象或者id类型对象的assign关键字会被转成weak,如图:

    090.png

    但是修饰Int/bool等数值型变量的assign不会自动转换成weak,如图:

    091.png

    关键字删除

    和手动内存管理相关的几个关键字,比如:release/retain/autorelease/super dealloc等会被删除;

    dealloc方法中如果除了release/super dealloc语句外,如果别的代码,dealloc方法会保留,如图:

    092.png

    如果没有整个方法都会被删除:

    093.png

    关键字替换

    在转换时block关键字会被自动替换成weak:

    094.png

    @autoreleasepool

    NSAutoreleasePool不支持ARC,会被替换成@autoreleasepool:

    095.png

    关于被宏注释代码

    使用宏定义的对象释放代码

    宏定义如下所示:

    1
    2
    #define RELEASE_SAFELY(__POINTER) { 
    [(__POINTER) release]; (__POINTER) = nil; }

    在执行ARC转换检查操作时,Xcode会在使用该宏的地方报错:

    096.png

    将该宏和使用该宏的地方删除即可。

    被宏注释掉的代码,Xcode在转换时是不会处理的,如图:

    097.png

    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

    Managing the Lifetimes of Objects from Nib Files

    Nib Memory Management

  • 相关阅读:
    python 基础 7.1 datetime 获得时间
    Python 学习笔记12
    Python 学习笔记11
    Python 学习笔记10
    Python 学习笔记9
    Python 学习笔记8
    Python 学习笔记7
    Python 学习笔记6
    Python 学习笔记5
    Python 学习笔记4
  • 原文地址:https://www.cnblogs.com/zhun/p/6376981.html
Copyright © 2011-2022 走看看