Swift进阶指南深入浅出iOSSwift开发

流着泪,做Swift3.0迁移适配

2016-09-19  本文已影响631人  电一闪

Swift3.0最大的变化,有太多太多,没有最大,只有一样大。
  先前工程除了第三方库,其余全部用Swift开发,也经历了2.1到2.2的迁移,2.2到2.3的迁移。在2.3版本时,编译器已经有了一些警告,某些语法将在Swift3.0中不能再使用,比如参数中的var声明,UIButton的构造方法等等。但让人没想到的是,Swift3.0真正到来时,更需要花费我2天的时间埋头改改改。谨以此文,记录迁移过程。

注意事项

手里两个业务工程用Swift迁移工具,其中有一个因为CocoaPods的原因,一直卡在Generating Preview的阶段。后来偶然因为CocoaPods由于GFW的原因不能执行update命令更新第三方库,我把CocoaPods去掉了,第三方库直接放主工程里,恢复原始工程结构,就可以正常迁移了。

自动迁移部分

  1. 已经声明的枚举值被全部改为首字母小写,因为新规范认为枚举相当于类,值是成员,那么成员就应当以驼峰法命名,首字母小写;
  2. 已经声明的方法被拆分调用。比如autolayout的封装库PureLayout,设置布局的方法autoPinEdgeToSuperviewEdge()变成autoPinEdge(toSuperviewEdge:);诸如此类还有很多,例如UIColor.whiteColor() -> UIColor.white......总之,尽可能的让能推断出类型的情况,少写重复词。当然,还有变长的方法名,例如view.hidden -> view.isHidden。
  3. 关于类成员变量的权限,迁移工具默认将先前的private权限改为fileprivate,因为新规范中的private就算在同文件中但不在同一个类里也是无访问权限的。因此先前的private权限,等同于现在的fileprivate。
  4. closure闭包现在有了更详细的宏声明了,声明闭包是否会作为存储型变量被强引用。因为对闭包的强引用会导致外部对象一直不能被释放,外部就需要使用weak var 来让closure对传入的对象只保持弱引用。不显式声明默认是非存储型闭包,当闭包被强引用则需要添加@escaping,位置在变量冒号后面,类型前面,例如
    failureCallback: @escaping ((_ errorMsg: String) -> Void)
  5. 方法声明参数现在分三部分,冒号左边一部分是标签,另一部分是参数名,冒号右边是变量类型。在Swift3.0以前,标签可有可无,没有标签时直接用变量名当标签使用;Swift3.0规范了方法命名,规定必须使用标签,如果强制不使用标签,那么方法声明时标签用一根下划线"_"替代。自动迁移工具无法完成更详细的识别,因此只能把大部分方法的标签先改为"_"。

手动迁移部分

  1. 按照新规范,其实所有的方法命名都应该重写。自动迁移让大部分方法的参数标签,都变为了""。这只是一个过渡的,能让程序快速通过编译的方案,如果有足够时间,方法名最好规范起来。
    例如
    func reloadUIWithData(
    data: BaseData?) {}
    新规范下,命名为:
    func reloadUI(withData data: BaseData?) {}

  2. 闭包作为参数声明时,标签只能用"_"。调用闭包则不需要标签,直接写值,多个参数值用","分隔。
    例如
    func request(_ redPacketID: Int64,
    successCallback: @escaping ((_ code: Int32, _ msg: String, _ rsp: RspLookRedPackage) -> Void),
    failureCallback: @escaping ((_ errorMsg: String) -> Void)) {
    }
    调用时,写:
    successCallback(rspMessage.rsp.retCode, rspMessage.rsp.retMsg, rspResult)

  3. Swift语言本身不支持同名方法重载,碰到需要重载的场景一般都是用不同数量参数,或不同的起始方法名来解决。如果第三方库中的方法名,被Swift3.0拆分成了相同起始方法名相同参数数量的方法,则需要Objective-C文件做桥接。
    SDWebImage库中,为UIImage下载图片有以下两个方法:
    - (void)sd_setImageWithURL:(NSURL *)url
    placeholderImage:(UIImage *)placeholder
    options:(SDWebImageOptions)options {}

     - (void)sd_setImageWithURL:(NSURL *)url 
               placeholderImage:(UIImage *)placeholder 
                      completed:(SDWebImageCompletionBlock)completedBlock { }
    

在Swift3.0中,会被识别成


Swift3.0拆分了部分方法起始名字

那么,在使用sd_setImage(with:....)时,编译器会报如下错:


方法具有二义性
解决方法就是创建一个Objective-C类别,添加起始方法名不同的方法名。
//头文件

#import <UIKit/UIKit.h>
#import <UIImageView+WebCache.h>

    @interface UIImageView (SDWebImageSwiftBridge)

    - (void)swiftBridge_sd_setImageWithURL:(NSURL *)url
                          placeholderImage:(UIImage *)placeholder
                                 completed:(SDWebImageCompletionBlock)completedBlock;

    @end
    //实现文件
    @implementation UIImageView (SDWebImageSwiftBridge)

    - (void)swiftBridge_sd_setImageWithURL:(NSURL *)url
                          placeholderImage:(UIImage *)placeholder
                                 completed:(SDWebImageCompletionBlock)completedBlock {
        [self sd_setImageWithURL:url placeholderImage:placeholder completed:completedBlock];
    }

    @end
  1. 以上全部完成后,编译运行,立即就发现了一个bug。比如声明的String!类型值,在用=赋值后,类型就变成了String?。在Swift3.0以前,String!类型的值传递或经方法返回后,可以直接当String类型的用。也就是说,如果现在String!需要传递或作为方法返回值,要么强制解包成String再传,要么不解包传到别处就是String?。String?对象在被使用时,假如值是"123",那么它将会是Optional("123")样式。比如拼接网址时,这样网址的网页就无法打开。
    写了更多的Swift3.0代码,发现了设计的根本所在。在Swift3.0中,所有声明为Optional的类型,直接打印的话都会被当做非强制解包类型。比如下面代码
Swift3.0中无论类型后是?还是!,直接打印都算有Optional()包裹
因此,以往String!类型对象打印值等同于String的情况,已经变成了String!打印值等同于String?,为Optional("")
我想,这样的变动,更多是出于严格区分普通类型和可选类型,进一步加强安全性的举措。之前的版本里,如果一个方法的返回值是String!类型,那么是可以直接当String来用;而现在,方法返回类型如果为String!,获取到的返回值实际上是String?。客观上说,String!的返回值也是可以为nil的,所以如果在外部直接使用获取的返回值,为nil的情况会直接导致crash。尤其是老旧的OC代码,没有_Nonnull或_Nullable修饰的返回值,在Swift中全部默认为强制解包类型,现在都要当?类型用了。
String!目前和String?最大共同点在于,如果当前值是Optional("123"),那么在使用该值是,将都是String?;另外,它们的区别就是,调对象方法或访问对象属性时,String!会直接以解包对象的方式去调,而String?仍然是可选链方法。详细见下图 Swift 3.0的可选类型示例
上一篇下一篇

猜你喜欢

热点阅读