Swift 5 新特性預覽(最低支援Xcode 10.2 beta版)

知識小集發表於2019-01-25

萬眾期待的 Swift 5 終於來了,蘋果爸爸答應的 ABI 穩定也終於來了。

小集新小夥伴 @NotFound-- 花時間將文件翻譯出來,供大家參考。翻譯不當之處,請及時留言指出,我們會持續更新。

App 瘦身

新特性

Swift 應用程式不再包含用於 Swift 標準庫的動態連結庫和用於執行 iOS 12.2watchOS 5.2tvOS 12.2 的裝置的構建變體中的 Swift SDK overlays。因此,當為 TestFlight 進行測試部署時,或者在為本地開發分發瘦身應用的 archive 包時,Swift 應用程式可以更小。

要對比 iOS 12.2 和 iOS 12.1(或更早版本) 瘦身後 App 的檔案大小差異,可以設定 App 的 deployment targetiOS 12.1 或更早版本,設定 scheme setGeneric iOS Device 並生成一個 App 的歸檔。在構建完成後,在 Archives organizer 選擇中 Distribute App,然後選擇 Development distribution。確保在 App Thinning 下拉選單中選擇一個特定裝置,如 iPhone XS。當分發完成後,在新建立的資料夾下開啟 App Thinning Size Report。iOS 12.2 系統的變體將小於 iOS 12.1 及更早的系統的變體。確切的大小差異取決於您的 App 使用的系統框架的數量。

關於 App 瘦身更多的資訊,可以檢視 Xcode Help 中的 What is app thinning?有關應用程式檔案大小的資訊,請參考 App Store Connect Help 中的 View builds and file sizes

Swift 語言

新特性

  • @dynamicCallable 允許您使用一個簡單的語法糖像呼叫函式一樣來呼叫命名型別。主要的應用場景是動態語言互操作。(SE-0216)

    例如:

@dynamicCallable struct ToyCallable {
    func dynamicCall(withArguments:[Int]){}
    func dynamicCall(withKeywordArguments:KeyValuePairs <String,Int>){}
}

let x = ToyCallable()

x(1,2,3)
// 等價於`x.dynamicallyCall(withArguments:[1,2,3])`

x(label: 1, 2)
// 等價於`x.dynamicallyCall(withKeywordArguments: ["label": 1, "": 2])`
複製程式碼
  • Key path 現在支援特性(identity) keypath (\.self),這是一個引用自身完整輸入值的 WritableKeyPath。(SE-0227)
let id = \Int.self
var x = 2
print(x[keyPath: id]) // Prints "2"
x[keyPath: id] = 3
print(x[keyPath: id]) // Prints "3"
複製程式碼
  • 在 Swift 5 之前,您可以編寫一個帶有可變引數的列舉 case:
enum X {
    case foo(bar: Int...) 
}
func baz() -> X {
    return .foo(bar: 0, 1, 2, 3) 
} 
複製程式碼

之前不是特意要支援這個特性,而且現在這樣寫會報錯了。

取而代之的是,讓列舉的 case 攜帶一個陣列,並顯式傳遞一個陣列

enum X {
    case foo(bar: [Int]) 
} 

func baz() -> X {
    return .foo(bar: [0, 1, 2, 3]) 
} 
複製程式碼
  • 在 Swift 5 中,帶有一個可選型別的表示式的 try? 將會展平生成的可選項,而不是返回巢狀的可選項。(SE-0230)

  • 如果型別T 符合Initialized with Literals中的其中一個協議(如 ExpressibleByIntegerLiteral),且 literal 是一個字面量表達示時,則 T(literal) 會使用相應的協議建立一個型別 T 的字面量,而不是使用一個協議的預設字面量型別的值來呼叫 Tinitializer

    如,類似於 UInt64(0xffff_ffff_ffff_ffff) 這樣的表示式現在是有效的,則之前會由於整型字面量的預設型別是 Int,而導致溢位。(SE-0213)

  • 提高了字串插值操作的效能、清晰性和效率。(SE-0228)

    舊的 _ExpressibleByStringInterpolation 協議被刪除;如果您有使用此協議的程式碼,則需要做相應更新。您可以使用 #if 條件判斷來區分 Swift 4.2Swift 5 的程式碼。例如:

#if compiler(<5)
extension MyType: _ExpressibleByStringInterpolation { /*...*/ }
#else
extension MyType: ExpressibleByStringInterpolation { /*...*/ }
#endif 
複製程式碼

Swift 標準庫

新功能

  • DictionaryLiteral 型別重新命名為 KeyValuePairs。(SE-0214)

  • 橋接到 Objective-C 程式碼的 Swift 字串現在可以在適當的時候從 CFStringGetCStringPtr返回一個 non-nil 值,同時從 -UTF8String 返回的指標與字串的生命週期相關聯,而不是最相近的那個 autorelease pool。如果程式正確,那應該沒有任何問題,並且會發現效能顯著提高。但是,這也可能會讓之前一些未經測試的程式碼執行,從而暴露一些潛在的問題;例如,如果有一個對 non-nil 值的判斷,而相應分支在 Swift 5 之前卻從未被執行過。(26236614)

  • Sequence 協議不再具有 SubSequence 關聯型別。先前返回 SubSequenceSequence 方法現在會返回具體型別。例如,suffix(_:)現在會返回一個 Array。(47323459)

    使用 SubSequenceSequence 擴充套件應該修改為類似地使用具體型別,或者修改為 Collection 的擴充套件,在 CollectionSubSequence 仍然可用。(45761817)

例如:

extension Sequence {
    func dropTwo() -> SubSequence {
        return self.dropFirst(2)
    }
}
複製程式碼

需要改為:

extension Sequence {
    func dropTwo() -> DropFirstSequence<Self> { 
        return self.dropFirst(2)
    }
}
複製程式碼

或者是:

extension Collection {
    func dropTwo() -> SubSequence {
        return self.dropFirst(2)
    }
}
複製程式碼
  • String結構的原生編碼將從 UTF-16 切換到 UTF-8,與 String.UTF16View 相比,這會提高相關聯的 String.UTF8View 的效能。重新對所有程式碼進行評審以提高效能,尤其是使用了 String.UTF16View 的程式碼。

Swift 包管理器

新功能

  • 現在,在使用 Swift 5 軟體包管理器時,Targets 可以宣告一些常用的針對特定目標的 build settings 設定。新設定也可以基於平臺和構建配置進行條件化處理。包含的構建設定支援 SwiftC 語言定義,C 語言標頭檔案搜尋路徑,連結庫和連結框架。(SE-0238)(23270646)

  • 在使用 Swift 5 軟體包管理器時,package 現在可以自定義 Apple 平臺的最低 deployment target。而如果 package A 依賴於 package B,但 package B 指定的最小 deployment target 高於 package A 的最小 deployment target,則構建 package A 時會丟擲錯誤。(SE-0236)(28253354)

  • 新的依賴映象功能允許頂層包覆蓋依賴 URL。(SE-0219)(42511642)

    使用以下命令設定映象:

$ swift package config set-mirror \
--package-url <original URL> --mirror-url <mirror URL>
複製程式碼
  • swift 測試命令可以使用標誌 --enable-code-coverage,來生成標準格式的程式碼覆蓋率資料,以便其它程式碼覆蓋工具使用。生成的程式碼覆蓋率資料儲存在 <build-dir>/<configuration>/codecov 目錄中。

  • Swift 5 不再支援 Swift 3 版本的軟體包管理器。仍然在使用 Swift 3 Package.swift 工具版本(tool-version)上的軟體包應該更新到新的工具版本上。

  • 對體積較大的包進行包管理器操作現在明顯更快了。

  • Swift 包管理器有一個新的 --disable-automatic-resolution 標誌項,當 Package.resolved 條目不再與 Package.swift 清單檔案中指定的依賴項版本相容時,該標誌項強制包解析失敗。此功能對於持續整合系統非常有用,可以檢查包的 Package.resolved 是否已過期。

  • swift run 命令有一個新的 --repl 選項,它會啟動 Swift REPL,支援匯入包的庫目標。這使您可以輕鬆地從包目標中試用 API,而無需構建呼叫該 API 的可執行檔案。

  • 有關使用 Swift 包管理器的更多資訊,請訪問 swift.org 上的 Using the Package Manager

Swift 編譯器

新特性

  • 現在,在優化(-O-Osize)構建中,預設情況下在執行時強制執行獨佔記憶體訪問。違反排他性的程式將在執行時丟擲帶有“重疊訪問”診斷訊息錯誤。您可以使用命令列標誌禁用此命令:-enforce-exclusivity = unchecked,但這樣做可能會導致未定義的行為。執行時違反排他性通常是由於同時訪問類屬性,全域性變數(包括頂層程式碼中的變數)或通過 eacaping 閉包捕獲的變數。(SR-7139

  • Swift 3 執行模式已被刪除。-swift-version 標誌支援的值為 44.25

  • 在 Swift 5 中,在 switch 語句中使用 Objective-C 中宣告的或來自系統框架的列舉時,必須處理未知的 case,這些 case 可能將來會新增,也可能是在 Objective-C實現檔案中私下定義。形式上,Objective-C 允許在列舉中儲存任何值,只要它匹配底層型別即可。這些未知的 case 可以使用新的 @unknown default case 來處理,當然如果 switch 中省略了任何已知的 case,編譯器仍然會給出警告。它們也可以使用普通的 default case 來處理。

    如果您已在 Objective-C 中定義了自己的列舉,並且不需要客戶端來處理 unknown case,則可以使用 NS_CLOSED_ENUM 巨集而不是 NS_ENUM。Swift 編譯器識別出這一點,並且不需求 switch 語句必須帶有 default case

    Swift 44.2 模式下,您仍然可以使用 @unknown default。如果省略 @unknown default,而又傳遞了一個未知的值,則程式在執行時丟擲異常,這與 Xcode 10.1 中的 Swift 4.2 上的行為是一致的。(SE-0192)(39367045)

  • 現在在 SourceKit 生成的 Swift 模組介面中會列印預設引數,而不僅僅是使用佔位符。

  • unownedunowned(unsafe) 型別的變數現在支援可選型別。

已知的問題

  • 如果引用了 UIAccessibility 結構的任何成員,則 Swift 編譯器會在 “Merge swiftmodule” 構建步驟中崩潰。構建日誌包含一條訊息:
Cross-reference to module 'UIKit'
... UIAccessibility
... in an extension in module 'UIKit'
... GuidedAccessError 
複製程式碼

包含 NS_ERROR_ENUM 列舉的其他型別也可能出現此問題,但 UIAccessibility 是最常見的。(47152185)

解決方法:在 targetBuild Setting -> Swift Compiler -> Code Generation 下,設定 Compilation Mode 的值為 Whole Module。這是大多數 Release 配置的預設設定。

  • 為了減小 Swift 後設資料的大小,Swift 中定義的 convenience initializers 如果呼叫了 Objective-C 中定義的一個 designated initializer,那隻會提前分配一個物件。在大多數情況下,這對您的程式沒有影響,但如果從 Objective-C 呼叫 convenience initializers,那麼 +alloc 分配的初始記憶體會被釋放,而不會呼叫任何 initializer。對於不希望發生任何型別的物件替換的呼叫者來說,這可能是有問題的。其中一個例子是 initWithCoder: :如果 NSKeyedUnarchiver 呼叫 Swift 實現 init(coder:) 並且存檔物件存在迴圈時,則 NSKeyedUnarchiver 的實現可能會出錯。

    在將來的版本中,編譯器將保證一個 convenience initializer 永遠不會丟棄它所呼叫的物件,只要它通過 self.init 委託給它的初始化程式也暴露給 Objective-C,或者是它在 Objective-C 中定義了,或者是使用 @objc 標記的,或者是重寫了一個暴露給 Objective-Cinitializer,或者是它滿足 @objc 協議的要求。(46823518)

  • 如果一個 keypath 字面量引用了 Objective-C 中定義的屬性,或者是在 Swift 中使用 @objcdynamic 修飾符定義的屬性,則編譯可能會失敗,並且報 “unsupported relocation of local symbol 'L_selector'” 錯誤,或者 key path 字面量無法在執行時生成正確的雜湊值或處理相等比較。

    解決方法:您可以定義一個不是 @objc 修飾的包裝屬性,來引用這個 key path。得到的 key path 與引用原始 Objective-C 屬性的 key path 不相等,但使用包裝屬性效果是相同的。

  • 某些專案可能會遇到以前版本的編譯時迴歸。

  • Swift 命令列專案在啟動時因丟擲 “dyld:Library not loaded” 錯誤而崩潰。

    解決方法:新增自定義的構建設定 SWIFT_FORCE_STATIC_LINK_STDLIB=YES

已解決的問題

  • 擴充套件繫結現在支援巢狀型別的擴充套件,這些巢狀型別本身是在擴充套件內定義的。之前可能會因為一些宣告順序而失敗,併產生 “未宣告型別” 錯誤。(SR-631)

  • 在 Swift 5 中,返回 Self 的類方法不能再被使用返回非 final 的具體類型別的方法來覆蓋。此類程式碼不是型別安全的,需要更新。(SR-695)

    例如:

class Base { 
    class func factory() -> Self { /*...*/ }
} 

class Derived: Base {
    class override func factory() -> Derived { /*...*/ } 
} 
複製程式碼
  • 在 Swift 5 模式下,現在會明確禁止宣告與巢狀型別同名的靜態屬性。以前,可以在泛型型別的擴充套件中執行這樣的宣告。(SR-7251)

    例如:

struct Foo<T> {}

extension Foo { 
    struct i {}

    // Error: Invalid redeclaration of 'i'.
    // (Prior to Swift 5, this didn’t produce an error.) 
    static var i: Int { return 0 }
}
複製程式碼
  • 現在可以在子類裡繼承父類中具有可變引數的初始化方法。

  • 在 Swift 5 中,函式中的 @autoclosure 引數不能再作為 @autoclosure 引數傳遞到另一個函式中呼叫。相反,您必須使用括號顯式呼叫函式值:();呼叫本身包含在一個隱式閉包中,保證了與 Swift 4 相同的行為。(SR-5719

    例如:

func foo(_ fn: @autoclosure () -> Int) {}
func bar(_ fn: @autoclosure () -> Int) {
    foo(fn) // Incorrect, `fn` can’t be forwarded and has to be called.
    foo(fn()) // OK
} 
複製程式碼
  • 現在完全支援在類和泛型中定義複雜的遞迴型別,而此前可能會導致死鎖。

  • 在 Swift 5 中,當將可選值轉換為泛型佔位符型別時,編譯器在解包值時會更加謹慎。這種轉換的結果現在更接近於非泛型上下文中的結果。(SR-4248)

    例如:

func forceCast<U>(_ value: Any?, to type: U.Type) -> U {
    return value as! U 
} 

let value: Any? = 42
print(forceCast(value, to: Any.self))
// Prints "Optional(42)"
// (Prior to Swift 5, this would print "42".)

print(value as! Any)
// Prints "Optional(42)"
複製程式碼
  • 協議現在可以將它們的實現型別限定為指定型別的子類。支援兩種等效形式:
protocol MyView: UIView { /*...*/ }
protocol MyView where Self: UIView { /*...*/ } 
複製程式碼

Swift 4.2 接受了第二種形式,但沒有完全實現,有時可能在編譯時或執行時崩潰。(SR-5581)

  • 在 Swift 5 中,當在屬性自身的 didSetwillSet 中設定屬性本身時,會避免遞迴呼叫(不論是隱式或顯式地設定自身的屬性)。(SR-419)

例如:

class Node {
    var children = [Node]() 
    var depth: Int = 0 {
        didSet { 
            if depth < 0 {
                // Won’t recursively call didSet, because this is setting depth on self. 
                depth = 0
            } 

            // Will call didSet for each of the children,
            // as this isn’t setting the property on self.
            // (Prior to Swift 5, this didn’t trigger property
            // observers to be called again.)
            for child in children { 
                child.depth = depth + 1
            } 
        }
    }
}
複製程式碼
  • Xcode中 的 diagnostics#sourceLocation 進行了支援。也就是說,如果您使用#sourceLocation 將生成的檔案中的行對映回原始碼時,diagnostics 會顯示在原始原始檔中的行數而不是生成的檔案中的。

  • 使用泛型型別別名作為引數或 @objc 方法的返回型別,不再導致生成無效的 Objective-C header。(SR-8697)

關注我們

歡迎關注我們的公眾號:iOS-Tips,也歡迎加入我們的群組討論問題。可以公眾號留言 iosflutter 等關鍵詞獲取入群方式。

Swift 5 新特性預覽(最低支援Xcode 10.2 beta版)

相關文章