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去掉了,第三方库直接放主工程里,恢复原始工程结构,就可以正常迁移了。
自动迁移部分
- 已经声明的枚举值被全部改为首字母小写,因为新规范认为枚举相当于类,值是成员,那么成员就应当以驼峰法命名,首字母小写;
- 已经声明的方法被拆分调用。比如autolayout的封装库PureLayout,设置布局的方法autoPinEdgeToSuperviewEdge()变成autoPinEdge(toSuperviewEdge:);诸如此类还有很多,例如UIColor.whiteColor() -> UIColor.white......总之,尽可能的让能推断出类型的情况,少写重复词。当然,还有变长的方法名,例如view.hidden -> view.isHidden。
- 关于类成员变量的权限,迁移工具默认将先前的private权限改为fileprivate,因为新规范中的private就算在同文件中但不在同一个类里也是无访问权限的。因此先前的private权限,等同于现在的fileprivate。
- closure闭包现在有了更详细的宏声明了,声明闭包是否会作为存储型变量被强引用。因为对闭包的强引用会导致外部对象一直不能被释放,外部就需要使用weak var 来让closure对传入的对象只保持弱引用。不显式声明默认是非存储型闭包,当闭包被强引用则需要添加@escaping,位置在变量冒号后面,类型前面,例如
failureCallback: @escaping ((_ errorMsg: String) -> Void) - 方法声明参数现在分三部分,冒号左边一部分是标签,另一部分是参数名,冒号右边是变量类型。在Swift3.0以前,标签可有可无,没有标签时直接用变量名当标签使用;Swift3.0规范了方法命名,规定必须使用标签,如果强制不使用标签,那么方法声明时标签用一根下划线"_"替代。自动迁移工具无法完成更详细的识别,因此只能把大部分方法的标签先改为"_"。
手动迁移部分
按照新规范,其实所有的方法命名都应该重写。自动迁移让大部分方法的参数标签,都变为了""。这只是一个过渡的,能让程序快速通过编译的方案,如果有足够时间,方法名最好规范起来。
例如
func reloadUIWithData( data: BaseData?) {}
新规范下,命名为:
func reloadUI(withData data: BaseData?) {}闭包作为参数声明时,标签只能用"_"。调用闭包则不需要标签,直接写值,多个参数值用","分隔。
例如
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)-
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中,会被识别成
那么,在使用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
-
以上全部完成后,编译运行,立即就发现了一个bug。比如声明的String!类型值,在用=赋值后,类型就变成了String?。在Swift3.0以前,String!类型的值传递或经方法返回后,可以直接当String类型的用。也就是说,如果现在String!需要传递或作为方法返回值,要么强制解包成String再传,要么不解包传到别处就是String?。String?对象在被使用时,假如值是"123",那么它将会是Optional("123")样式。比如拼接网址时,这样网址的网页就无法打开。
写了更多的Swift3.0代码,发现了设计的根本所在。在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?仍然是可选链方法。详细见下图