Swift 5 在2019年3月25日正式release,主要最廣為人討論的就是ABI Stability,也就是Swift runtime將被放在作業系統裡而不是包含在App裡,更進一步的說明可以參考ABI Stability and More 和 Evolving Swift On Apple Platforms After ABI Stability。
此外,Swift 5語言本身也增加了一些新的特性,包含新的語法及標準函式庫的更新。本篇文章將概略說明部分較常被提出的新特性。關於完整的Swift 5更新內容請參考Swift部落格的這篇文章:Swift 5 Released!
Raw Strings SE-0200
在字串前後加上#符號,即可使用raw string
在raw string中,反斜線與雙引號都可以直接使用,不再需要額外的跳脫字元
let rawStr = #"Hello, \ My "dear" friend!"#
print(rawStr)
印出結果:
Hello, \ My "dear" friend!
若依然要使用跳脫字元,使用反斜線加上#符號:
let rawStr = #"Hello,\#nMy friend!"#
print(rawStr)
如此才可印出有換行的結果:
Hello,
My friend!
你可以改在字串前後加上兩個或更多個#符號,使用raw string。如此一來,即可改在字串中直接輸入"#;此外,跳脫字元變也為\##
let rawStr = ##"Hello,\##n Now yout can type "# directly!"##
print(rawStr)
印出結果:
Hello,
Now yout can type "# directly!
Customizing string Interpolation SE-0228
String Interpolation是swift在字串中放入變數的方式之一,如下:
let name = "myitem"
let weight = 123
print("The item is \(name). Its weight is \(weight).")
//印出The item is myitem. Its weight is 123.
然而,若是自訂的類別物件,在以往,需要令該類別實作CustomStringConvertible的description property,才能印出自訂的內容;類似Java的toString。
而現在,在Swift 5中,可以使用extension替String.StringInterpolation增加新的appendInterpolation方法,以處理自訂的類別。
class MyItem {
let name = "item name"
let weight = 123
init(name:String, weight:Int) {
self.name = name
self.weight = weight
}
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: MyItem) {
appendInterpolation("The item is \(value.name). Its weight is \(value.weight).")
}
}
let myitem = MyItem(name:"item name", weight:123)
print("myitem:\(myitem)")
//印出myitem:The item is myitem. Its weight is 123.
此外,在增加新的appendInterpolation方法時,
你也可以使用多個參數,以及使用argument label,就像自訂函數一樣,進一步客製想要的String Interpolation內容:
extension String.StringInterpolation {
mutating func appendInterpolation(mystring: String, replace oldString:String, with newString:String) {
let newstr = mystring.replacingOccurrences(of: oldString, with: newString)
appendInterpolation(newstr)
}
}
print("test:\(mystring:"aaabbbccc", replace:"bbb", with:"ddd")")
//印出test:aaadddccc
Standard Result type SE-0235
Swift 5 標準函式庫中加入了Result型別,讓程式開發者可以更一致地處理錯誤。
Result是一個enum,有success與failure兩個case,並分別各有一個associated value,分別代表執行成功時欲回傳的資料,以及執行失敗時發生的錯誤。
public enum Result<Success, Failure: Error> {
case success(Success), failure(Failure)
}
假設我們使用要定義一個取得新聞資料的API,並使用Result表示結果,可以定義如下:
func fetchNews(from urlString: String, completionHandler: @escaping (Result<MyNews, MyNewsError>) -> Void) {
guard let url = URL(string: urlString) else {
//發生錯誤時回傳.failure及錯誤內容
completionHandler(.failure(.networkError))
return
}
var mynews = MyNews()
// ...
//正確執行時,回傳.success及資料
completionHandler(.success(mynews))
}
MyNews是某個自訂的類別;MyNewsError則是自訂的enum,繼承自標準函式庫的Error。
class MyNews {
//...
}
enum MyNewsError: Error {
case networkError
}
如此一來,在呼叫fetchNews取新聞資料時,可以用switch case以更直覺簡單地分別處理成功與失敗的結果:
fetchNews(from: "https://www.xxx.com") { result in
switch result {
case .success(let mynews):
// ...
case .failure(let myerror):
// ...
}
}
Dynamically callable types SE-0216
dynamicCallable可以讓你定義一個型別,其宣告出來的變數可以如同函式般直接使用,如下:
@dynamicCallable
struct MyMultiplier {
private num:Int
init(num:Int) {
self.num = num;
}
func dynamicallyCall(withArguments args: [Int]) -> Int {
//將陣列中的所有元素乘以10後相加。
return args.map { $0 * num }.reduce(0,+);
}
}
建立MyMultiplier型別的物件,並令其當作函式來呼叫:
let m10 = MyMultiplier(num:10)
m10(1,2,3,4,5) //150
如上,該型別必須實作dynamicallyCall方法,而參數可以是withArguments任意型別的陣列,如上例為Int陣列。
此外,也可以改用withKeywordArguments,並把參數改為KeyValuePairs<String, Any>,讓函式可以取得傳入的參數名稱。
@dynamicCallable
struct MyFunc {
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Any>) {
//...
}
}
let myfunc = MyFunc()
myfunc(name:"test name", count:123)
dynamicCallable可以用於struct, class, enum。
Handling Future Enumeration Cases SE-0192
當switch述句中的條件判斷若其值來自非自定義的enum,如:C enums或是來自standard library的enum,則意味著此switch述句的條件判斷值,在未來可能需要處理非預期的內容;而通常在這種情況下,我們一定會用到default來處理。
以下以來自standard library的enum: AVAudioSession.InterruptionType為例。
Standard library中AVAudioSession.InterruptionType的定義如下:
public enum InterruptionType : UInt {
case began
case ended
}
假設我們有一個switch述句需要針對AVAudioSession.InterruptionType進行處理:
//判斷AudioInterruption的狀態並印出log...
func logAudioInterruptionType(inttType:AVAudioSession.InterruptionType) {
switch inttType {
case .began:
print("began:\(inttType)")
case .ended:
print("ended:\(inttType)")
}
由於AVAudioSession.InterruptionType是來自standard library的enum,未來有可能會增加新的值;所以編譯器提出警告:
Switch covers known cases, but 'AVAudioSession.InterruptionType' may have additional unknown values, possibly added in future versions
Handle unknown values using "@unknown default"
使用 @unknown 標註,暗示未來隨著標準函式庫的更新,此switch case有可能會遇到其他未知的enum類型:
func logAudioInterruptionType(inttType:AVAudioSession.InterruptionType) {
switch inttType {
case .began:
print("began:\(inttType)")
case .ended:
print("ended:\(inttType)")
@unknown default:
print("unknown type:\(inttType)")
}
}
為何不直接寫上default?
如果直接寫個default,雖然也不會出現編譯錯誤,
func logAudioInterruptionType(inttType:AVAudioSession.InterruptionType) {
switch inttType {
case .began:
print("began:\(inttType)")
case .ended:
print("ended:\(inttType)")
default: //不太合理,因為目前看來不可能...
print("unknown type:\(inttType)")
}
}
//備註:在Swift語言中,switch裡的每個case執行完就會自動跳出,不需要像C語言一樣使用break來防止繼續往下執行到其他case
但就邏輯上來說,這是令人困惑的,
因為就現況而言,AVAudioSession.InterruptionType確實只有 .began 與 .ended 兩種值;
若是自定義的enum,而你的 switch case 也已明確地處理好所有enum值的話,再加上default,編譯器甚至還會回報Warning,請你將這個累贅的 default陳述句 移除:
Default will never be executed
一般的default應該用於如下情境,switch case中尚有值仍未處理的情況;
public enum MyGreetings {
case .goodmorning
case .goodnight
}
func printMyGreetings(greetingType:MyGreetings) {
switch greetingType {
case .goodmorning:
print("Good Morning")
default: //合理,因為還有goodnight沒有處理到...
print("Good night...I think...")
}
而加上 @unknown 則意味著,此處的 default 可能目前不會遇到,但是在往後有可能會遇到的,所以通常會是來自 非自定義的enum 或是C enums。
Proposal SE-0192 就是希望能在程式中刻意區分這一點而提出,也才有了用 @unknown 來處理future enumeration的這個變動。
This proposal aims to distinguish between enums that are frozen (meaning they will never get any new cases) and those that are non-frozen, and to ensure that clients handle any future cases when dealing with the latter.
Flatten nested optionals resulting from ‘try?’ SE-0230
因爲try而造成的nested optionals,意即如:String?? 這樣有兩個 ?? 的情況,在swift 5之後也會變為普通的optional。如:
class MyItem {
init?(itemid:String) {
//假設init可能會因為不適合的itemid而建立失敗
}
//回傳String,但可能拋出錯誤
func getItemInfo() throws -> String {
///...
}
//回傳String?
func getItemDesc() -> String? {
///...
}
}
//getItemInfo可能拋出錯誤,需使用try處理
let myItemInfo = try? MyItem(itemid:"aaa")?.getItemInfo()
在swift 5 之前,myItemInfo為String??
在swift 5 之後,myItemInfo為String?
這個改變是因為,原本多數的 optional 使用情境,都會避免發生 nested optionals,如下:
//getItemDesc方法不會拋出錯誤,而是回傳Optional String
let myItemDesc = MyItem(itemid:"aaa")?.getItemDesc()
上面程式中的myItemDesc為String?,
並不會因為 initializer 回傳 optional 型態,而getItemDesc方法也回傳 optional 型態,
進而造成有兩個 ?? 的nested optionals。
因此,於Swift 5中,為了使 try? 與其他 optional 的使用情境更一致,而做了此更動。
Reference
https://swift.org/blog/swift-5-released/
What’s new in Swift 5.0 – Hacking with Swift
沒有留言:
張貼留言