阿里云App从Swift 2.1开始使用Swift,随时不断的推进,现在所有的业务代码都用Swift编写。由于Swift 3.0语法上有诸多改变,所以从Swift 2.3升级到Swift 3.0是一件宜早不宜迟的事情。元旦期间抽了点时间做这个升级。
//TODO: Swift 3.0 小明
这样的注释。保证主工程能运行通过之后,大家并行解决这些TODO问题。Swift 3.0改变最大的地方如下所示。
//Swift 2.3
label.textAlignment = .Right
//Swift 3.0
label.textAlignment = .right
//Swift 2.3
label.textColor = UIColor.redColor()
//Swift 3.0
label.textColor = UIColor.red
//argument label构成重载
func foo(x: Int) {}
func foo(foo x: Int) {}
func foo(bar x: Int) {}
foo(x:)(5)
foo(foo:)(5)
foo(bar:)(5)
//可以随意赋值给参数类型相同的函数
var fn1 : (Int) -> Void
fn1 = foo(x:)
fn1 = foo(foo:)
fn1 = foo(bar:)
var Fooblock : ((Int, Int) -> Void) = { (a, b) in
print("\(a) \(b)")
}
//这样写也OK
var Fooblock : ((_ a: Int, _ b : Int) -> Void) = { (a, b) in
print("\(a) \(b)")
}
//用的时候没有任何arguement label
fooBlock(3, 4)
//Swift 2.3
label.font = UIFont.systemFontOfSize(17)
//Swift 3.0
label.font = UIFont.systemFont(ofSize: 17)
有些OC方法被改得连爹妈都不认识了,比如OC ALYGetURLParams->Swift alyGetParams
。
//Swift 2.3
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
override public func intrinsicContentSize() -> CGSize {
}
//Swift 3.0
override var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
override var intrinsicContentSize: CGSize {
}
,
。//Swift 3.0
let foo : Float? = 1.0
let bar : Float? = 2.0
if let _foo = foo,
let _bar = bar,
_foo > _bar {
}
为了避免编译失败,Xcode会自动给写了Optional比较的老代码的文件顶上加上下面这段代码。
// FIXME: comparison operators with optionals were removed from the Swift Standard Libary.
// Consider refactoring the code to use the non-optional operators.
fileprivate func < (lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
// FIXME: comparison operators with optionals were removed from the Swift Standard Libary.
// Consider refactoring the code to use the non-optional operators.
fileprivate func > (lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l > r
default:
return rhs < lhs
}
}
unused
警告,需要_
接一下。_ = self.navigationController?.popViewController(animated: true)
自己定义的接口可以使用@discardableResult
消除警告,对于链式构造函数来说非常有用。
@discardableResult
open class func routeURL(_ url: URL?) -> Bool {
return JLRoutes.routeURL(url)
}
@escaping
修饰符。@escaping
不构成重载,但是成为判断是否实现协议接口的依据
。public typealias FooBlock = () -> Void
func FooFunc(_ block: FooBlock) {
}
//@escaping 并不会构成重载,声明下面两个函数会报redeclaration错误。
//func FooFunc(_ block: @escaping FooBlock) {
//}
protocol FooProtocol {
func doBlock(block: @escaping FooBlock)
}
//但是@escaping会影响实现协议接口
class FooClass : FooProtocol {
//OK
func doBlock(block: @escaping () -> Void) {
block()
}
//会提示没有实现FooProtocol
// func doBlock(block: () -> Void) {
// block()
// }
}
//Swift 2.3
let delayTime = dispatch_time(DISPATCH_TIME_NOW, 0.5)
dispatch_after(delayTime, queue, {
block()
})
//Swift 3.0
DispatchQueue.main.asyncAfter(deadline: 0.5, execute: {
block()
})
//Swift 3.0
CGPoint(x: 0, y: 0)
CGSize(width: 500, height: 500)
CGRect(x: 0, y: 0, width: 500, height: 500)
CGPoint.zero
CGSize.zero
CGRect.zero
open
、fileprivate
等关键字。需要被继承的类、需要被override的方法,都要用open修饰。extension不能用open修饰,但是它的方法只要加了open修饰,也可以在子类里面override。Protocol Composition
语法。//swift 2.x
typealias CompositeType = protocol
//swift 3.0
typealias CompositeType = Interface1 & Interface2 & Interface3
sizeof
,统一使用MemoryLayout
来计算内存大小。指针的使用更加严格。
UnsafePointer, UnsafeMutablePointer
来表示void *
。在swift 3.0中,UnsafePointer, UnsafeMutablePointer
表示指针指向的内存必须是绑定数据类型的,新增了UnsafeRawPointer , UnsafeMutableRawPointer
来表示指针指向的内存并没有被绑定具体的数据类型。所以UnsafePointer, UnsafeMutablePointer
都需要用UnsafeRawPointer , UnsafeMutableRawPointer
来替代。UnsafePointer
到UnsafePointer
的转换是不允许的,这是一种很危险的,不可预知的行为。不同类型指针的转换必须要用指定的函数://指针类型转换函数:
1、UnsafePointer.withMemoryRebound(to:capacity:_)
2、UnsafeRawPointer.assumingMemoryBound(to:)
3、UnsafeRawPointer.bindMemory(to:capacity:)
//简单示例:
//swift 2.x
let ptrT: UnsafeMutablePointer = UnsafeMutablePointer.alloc(1)
ptrT.memory = 3
let ptrU = UnsafePointer(ptrT)
//上面的写法在swift 3.0会报错。
//error: 'init' is unavailable: use 'withMemoryRebound(to:capacity:_)' to temporarily view memory as another layout-compatible type.
//swift 3.0
let ptrT: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1)
ptrT.pointee = 3
var ptrU: UnsafeMutablePointer?
ptrT.withMemoryRebound(to: UInt.self, capacity: 1) { ptr in
ptrU = ptr
}
NS_Options
类型会转换成OptionSet
,而等于0的那一项作为默认值是看不到的,非常诡异。默认值可以通过AspectOptions(rawValue: 0)
和[]
得到。//OC
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// Called after the original implementation (default)
AspectPositionInstead = 1, /// Will replace the original implementation.
AspectPositionBefore = 2, /// Called before the original implementation.
AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};
//Swift
public struct AspectOptions : OptionSet {
public init(rawValue: UInt)
/// Called after the original implementation (default)
public static var positionInstead: AspectOptions { get } /// Will replace the original implementation.
/// Will replace the original implementation.
public static var positionBefore: AspectOptions { get } /// Called before the original implementation.
/// Called before the original implementation.
public static var optionAutomaticRemoval: AspectOptions { get } /// Will remove the hook after the first execution.
}
as
转型。Any和AnyObject的区别可以看到这篇文章:ANY 和 ANYOBJECT。[AnyHashable: Any]
,很多时候还得转回Dictionary/NSDictionary继续使用,好在as
转型也是OK的。typedef void (^LOGIN_COMPLETION_HANDLER) (BOOL isSuccessful, NSDictionary* loginResult);
id
也会变成Any
类型,用的时候需要强转一下,比较恶心。所以要使用更新的instanceType
吧。//会返回Any
+ (id) sharedInstantce;
//用的时候需要不断强转
(TBLoginCenter.sharedInstantce() as! TBLoginCenter).login()
//要用下面这种
+ (instancetype) sharedInstance;
ImplicitlyUnwrappedOptional
语义变了,不会自动解包了。以前如果用到IUO的地方需要注意,要不然可能会出现下面这种情况。客户端的问题很容易发现,传递给服务器端的参数如果也有同样的问题会很蛋疼,因此可以考虑在网络底层检查一下参数是否包含Optional
。如果发现有,那么直接abort掉。
Error
对象,那么会跟Swift Error冲突,可以用Swift.Error
这种方式解决。override open func webView(_ webView: UIView!, didFailLoadWithError error: Swift.Error!) {
super.webView(webView, didFailLoadWithError: error)
}
- (id _Nonnull)initWithFileInfo:(ARUPFileInfo * _Nonnull)fileInfo
bizeType:(NSString *_Nonnull)bizType
propress:(ProgressBlock _Nullable )progress
success:(SuccessBlock _Nullable )success
faile:(FailureBlock _Nullable )faile
networkSwitch:(NetworkSwitchBlock _Nullable)networkSwitch
error:(NSError *_Nonnull*_Nonnull)error;
苹果官方bug系统上也有人提过这个问题,参考:https://bugs.swift.org/browse/SR-3272。目前的解决方案是希望ARUP SDK针对swift 3出个适配版,去掉Nonnull修饰即可通过编译。
升级Swift 3.0之后,感觉编译贼慢。根据:Profiling your Swift compilation times这篇文章的方法加上-Xfrontend -debug-time-function-bodies
之后,发现排名前五的方法都是50s左右。总的编译时间比2.3要慢一倍。2.3和3.0版本编译都很慢,但是3.0要更慢。
Swift 2.3: 342,403ms
Swift 3.0: 579,519ms
我顿时感到我大好青春都浪费在编译上面了,所以我们赶快来看看这段代码写了什么东西。
override func fetchDataForSinglePageTableView(_ actionType: ALYLoadDataActionType, successCallback: @escaping GetPageDataSuccessCallback, failedCallback: @escaping GetPageDataFailedCallback) {
//blablabla
successCallback(UInt((self?.statusInfo.count ?? 0) + (self?.regularInfo.count ?? 0) + (self?.nameServerInfo.count ?? 0)))
}) { [weak self](exception) -> Void in
failedCallback(exception)
self?.refreshButton.isHidden = false
self?.showFailureToast(exception.reason)
}
}
这么大一段函数,初看没有明确的目标,于是我查找了资料,看看是否有前人的经验可以借鉴,结果果然有很多人遇到相同的问题,现有的总结已经很详细我不再赘述,这里主要参考了:Swift 工程速度编译慢。对比这篇总结,我猜测应该是下面这行将几个连续的??
相加导致的。
//老代码
successCallback(UInt((self?.statusInfo.count ?? 0) + (self?.regularInfo.count ?? 0) + (self?.nameServerInfo.count ?? 0)))
//新代码
let statusCnt = self?.statusInfo.count ?? 0
let regularCnt = self?.regularInfo.count ?? 0
let nameServerCnt = self?.nameServerInfo.count ?? 0
successCallback(UInt(statusCnt + regularCnt + nameServerCnt))
再跑一下测试命令,编译时间马上变成78ms,差了将近1000倍!
78.3ms /Users/chantu/test3/cloudconsole-iOS/CloudConsoleApp/Domain/Domain+Register/ALYDomainRegisterMsgViewController.swift:102:19 @objc override func fetchDataForSinglePa geTableView(_ actionType: ALYLoadDataActionType, successCallback: @escaping GetPageDataSuccessCallback, failedCallback: @escaping GetPageDataFailedCallback)
基于这个思路,我主要修改了以下两种情况的代码。
??
同时出现在一个表达式里面的,如上述代码??
出现在字典里面的,如下面这种。var param:[String: String] = [
"securityGroupId": self.belongGroupId ?? "",
"regionId": self.regionId ?? "",
"ipProtocol": self.protocolType ?? "",
"portRange": self.portRange ?? "",
"policy": self.policy ?? "",
"priority": self.priority ?? "",
"nicType": self.nicType ?? ""
]
为了保持写代码的流畅性,不因为编译问题影响大家编码的体验,因此我只对几个特别耗时的地方做了修改,但是可以从测试结果看到,编译速度有了明显的提升,下面是测试后跑出来的时间。可以看到最慢的也只有1s多了,比之前的47s好太多了。
1289.2ms /Users/chantu/test3/cloudconsole-iOS/CloudConsoleApp/Domain/DomainRealNameVerify/ALYDomainRealNameVerifyUploadInfoDataService.swift:117:10 func uploadInfo(_ templateId: String, credentialsNo: String, credentialsType: ALYDomainRealNameVerifyCredentialsType, credentialsImageData: Data, completionBlock: @escaping ((_ isSuccess: Bool, _ errorMsg: String) -> Void))
1084.8ms /Users/chantu/test3/cloudconsole-iOS/CloudConsoleApp/Instance/ECS/Disk/ALYECSDiskDetailViewController.swift:242:10 func setcellContentsWithModel(_ model: ALYECSDiskDetailModel)
1038.6ms /Users/chantu/test3/cloudconsole-iOS/CloudConsoleApp/Instance/ECS/Disk/ALYECSDiskDetailViewController.swift:242:10 func setcellContentsWithModel(_ model: ALYECSDiskDetailModel)
1027.7ms /Users/chantu/test3/cloudconsole-iOS/CloudConsoleApp/Instance/Data/ALYCloudMetricDataService.swift:15:10 func getInstanceMetric(withPluginId pluginId: String, dimensions: String, metric: String, startTime: Double, endTime: Double, successCallback: @escaping ALYServiceSuccessCallback, failureCallback: @escaping ALYServiceFailureCallback)
999.3ms /Users/chantu/test3/cloudconsole-iOS/CloudConsoleApp/Domain/DomainRealNameVerify/ALYDomainRealNameVerifyUploadInfoDataService.swift:117:10 func uploadInfo(_ templateId: String, credentialsNo: String, credentialsType: ALYDomainRealNameVerifyCredentialsType, credentialsImageData: Data, completionBlock: @escaping ((_ isSuccess: Bool, _ errorMsg: String) -> Void))
我们目前的做法是尽量不把这些复杂的操作写到一个表达式里面,先把变量存起来再放到表达式里计算,虽然是因为语言的问题不得不妥协但为了自己编译速度还是宁可多写几行。
因为Swift 3.0版本开始保证接口的稳定性,这次升级到3.0之后,使用Swift再无后顾之忧。希望苹果真的不要再干出Swift 1.0->2.0->3.0每次升级都要改大量代码的扯淡事。