Swift 5新特性詳解:ABI 穩定終於來了!

weixin_33807284發表於2019-01-28

近日,蘋果開發者部落格更新了一篇關於Swift 5的文章,帶來了Swift 5新特性的訊息,其中最受開發期待的莫過於iOS 12.2將帶來ABI 穩定性,這意味著基礎庫將植入系統中,不再包含在App中,應用程式的體積會更小,更多新功能請看下文。

App瘦身

新功能

Swift應用程式不再包含用於Swift標準庫和Swift SDK(執行iOS 12.2、watchOS 5.2和tvOS 12.2的裝置的構建變體)的動態連結庫。因此,在使用TestFlight進行測試時,或者為本地開減小應用程式體積時,Swift應用程式可以變得更小。

要檢視iOS 12.2和iOS 12.1(或更早版本)應用程式之間的檔案大小差異,請將應用程式的部署目標設定為iOS 12.1或更早版本,將scheme設定為Generic iOS Device,然後建立應用程式壓縮包。

在構建好壓縮包之後,從壓縮包管理器中選擇Distribution App,然後選擇Development Distribution。確保在App Thinning下拉選單中選擇特定的裝置,比如iPhone XS。這個過程完成後,在新建立的資料夾中開啟App Thinning Size Report。iOS 12.2的體積會比iOS 12.1或更早版本的體積小。具體的大小差異取決於應用程式使用的框架的數量。

Swift

@dynamicCallable屬性允許你呼叫命名的型別,就像使用簡單的語法糖呼叫函式一樣。主要的應用場景是動態語言互操作性。

例如:

@dynamicCallable struct ToyCallable {    func dynamicallyCall(withArguments: [Int]) {}    func dynamicallyCall(withKeywordArguments: KeyValuePairs\u0026lt;String, Int\u0026gt;) {}}let x = ToyCallable()x(1, 2, 3)// Desugars to `x.dynamicallyCall(withArguments: [1, 2, 3])`x(label: 1, 2)// Desugars to `x.dynamicallyCall(withKeywordArguments: [\u0026quot;label\u0026quot;: 1, \u0026quot;\u0026quot;: 2])`

現在支援標識KeyPath(.self),一個引用其整個輸入值的WritableKeyPath

let id = \\Int.selfvar x = 2print(x[keyPath: id]) // Prints \u0026quot;2\u0026quot;x[keyPath: id] = 3print(x[keyPath: id]) // Prints \u0026quot;3\u0026quot;

在Swift 5之前,你可以編寫一個帶有可變引數的列舉:

enum X {    case foo(bar: Int...) }func baz() -\u0026gt; X {    return .foo(bar: 0, 1, 2, 3) } 

現在如果這麼做會出錯。相反,現在引數改成了一個陣列,並且需要顯式傳入陣列:

enum X {    case foo(bar: [Int]) } func baz() -\u0026gt; X {    return .foo(bar: [0, 1, 2, 3]) } 

在Swift 5模式下,可以用?和Optional型別表示式來扁平化生成的Optional,而不是返回巢狀的Optional。

如果型別T符合這些字面量初始化中的一個——例如ExpressibleByIntegerLiteral——並假設literal是一個字面量表示式,那麼T(literal)就建立了一個T型別的字面量。

例如,UInt64(0xffff_ffff_ffff_ffff)現在是有效的,而之前它們會導致預設整型字面量型別Int溢位。

字串插值的效能、清晰度和效率得到了改進。

舊的_ExpressibleByStringInterpolation協議被移除,如果你的程式碼使用了這個協議,需要更新這些程式碼,你可以使用#if在Swift 4.2和Swift 5之間條件化程式碼。例如:

#if compiler(\u0026lt;5)extension MyType: _ExpressibleByStringInterpolation { /*...*/ }#elseextension MyType: ExpressibleByStringInterpolation { /*...*/ }#endif 

Swift標準庫

  • DictionaryLiteral型別被重命為KeyValuePairs。

  • 與Objective-C程式碼橋接的Swift字串現在會在適當的時候從CFStringGetCStringPtr返回一個非空值,而且從-UTF8String返回的指標與字串的生命週期(而不是最裡面的autorelease pool)相關聯。正確的程式應該不會有任何問題,而且還會帶來效能方面的提升。但是,它可能會導致以前未經測試的程式碼暴露出潛在的錯誤。

  • Sequence協議不再具有SubSequence關聯型別。之前返回SubSequence的Sequence方法現在返回的是具體的型別。例如,suffix(_:)現在返回Array。

使用SubSequence的Sequence擴充套件應該修改為使用具體的型別,或者修改為Collection的擴充套件(此時SubSequence仍然可用)。

例如:

extension Sequence {    func dropTwo() -\u0026gt; SubSequence {        return self.dropFirst(2)    }}

變為:

extension Sequence {    func dropTwo() -\u0026gt; DropFirstSequence\u0026lt;Self\u0026gt; {         return self.dropFirst(2)    }}

或者:

extension Collection {    func dropTwo() -\u0026gt; SubSequence {        return self.dropFirst(2)    }}
  • String結構的原生編碼從UTF-16切換到UTF-8,這樣提高了String.UTF8View的效能(相對於String.UTF16View)。

Swift包管理器

  • 現在,在使用Swift 5 Package.swift工具版本時,可以宣告一些常用的特定於目標的構建設定。新的設定也可以基於平臺和構建配置進行條件化。構建設定支援Swift和C語言定義、C語言標頭檔案搜尋路徑、連結庫和連結框架。

  • 現在,在使用Swift 5 Package.swift工具版本時,可以為Apple平臺自定義最低部署目標。如果程式包的任何依賴項指定的最小部署目標大於程式包自身的最低部署目標,就會丟擲錯誤。

  • 新的依賴項映象功能允許頂層包覆蓋依賴項URL。

可以使用以下命令設定映象:

$ swift package config set-mirror \\--package-url \u0026lt;original URL\u0026gt; --mirror-url \u0026lt;mirror URL\u0026gt;
  • swift test命令提供了–enable-code-coverage標誌,它生成的程式碼覆蓋率資料也適用於其他程式碼覆蓋工具。生成的程式碼覆蓋率資料放在//codecov目錄中。

  • Swift 5不再支援Swift 3 Package.swift工具版本。Swift 3 Package.swift工具版本的軟體包應該升級到更新的工具版本。

  • 針對較大的程式包的包管理器操作現在明顯更快。

  • Swift包管理器提供了一個新的–disable-automatic-resolution標誌,當Package.resolved條目與Package.swift清單檔案中指定的依賴項版本不相容時,該標誌會強制包解析失敗。在進行持續整合時,如果需要檢查包的Package.resolved是否已過期,這項功能會非常有用。

  • swift run命令提供了一個新的——repl選項,它將啟動Swift REPL,支援匯入包的庫目標。這樣你就可以輕鬆地試用API,而無需構建呼叫該API的可執行檔案。

Swift編譯器

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

  • Swift 3模式已被刪除。-swift-version標誌支援的值為4、4.2和5。

  • 在Swift 5模式中,在迭代使用Objective-C宣告或來自系統框架的列舉時需要處理未知的case——可能在將來新增的case,或者可能在Objective-C實現檔案中私下定義的case。Objective-C允許在列舉中儲存任意值,只要它們與底層型別匹配即可。可以使用新的@unknown來處理這些未知case,當然也可以使用普通的default來處理它們。

如果你已在Objective-C中定義了自己的列舉,並且不需要客戶端處理未知case,那麼可以使用NS_CLOSED_ENUM巨集而不是NS_ENUM。Swift編譯器就會識別出來,不要求在迭代時提供預設case。

在Swift 4和4.2模式下,你仍然可以使用@unknown。如果省略了它並傳入了一個未知的值,程式將在執行時出錯,這與Xcode 10.1中的Swift 4.2的行為是一樣的。

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

  • unowned和unowned(unsafe)變數現在支援Optional。

已知問題

  • 如果引用了UIAccessibility的成員,Swift編譯器會在進行到“Merge swiftmodule”這個構建步驟時崩潰。構建日誌會包含這樣一條訊息:
Cross-reference to module 'UIKit'... UIAccessibility... in an extension in module 'UIKit'... GuidedAccessError

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

解決方法:使用“Swift Compiler - Code Generation”下的Whole Module編譯模式選項重新構建,這是大多數釋出配置的預設設定。

  • 為了減少Swift後設資料佔用的空間,Swift中定義的便捷初始化器現在只在呼叫Objective-C中定義的指定初始化器時提前分配物件。在大多數情況下,這對程式沒有任何影響,但是如果從Objective-C呼叫便捷初始化器,就會釋放+alloc初始分配的資源。對於不希望發生物件替換的初始化器使用者來說,這可能是有問題的。例如,在使用initWithCoder:時,NSKeyedUnarchiver的實現可能會不正確,如果它呼叫了init(coder:)的Swift實現,並且物件圖中包含了環。

  • 如果KeyPath字面量引用了在Objective-C中定義的屬性或者在Swift中使用@objc和動態修飾符定義的屬性,那麼編譯可能會失敗,並丟擲“unsupported relocation of local symbol ‘L_selector’”的錯誤,或者KeyPath可能無法在執行時生成正確的雜湊值。

解決方法:你可以自己定義非@objc包裝器屬性,指向這個KeyPath。生成的KeyPath與引用原始Objective-C屬性的KeyPath不一樣,但使用效果是一樣的。

  • 某些專案可能會遇到編譯時迴歸問題。

  • Swift命令列專案在啟動時會崩潰,錯誤為“dyld: Library not loaded”。

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

已解決的問題

  • 擴充套件繫結現在支援巢狀型別的擴充套件,這些型別本身是在擴充套件內定義的。之前可能會因為宣告順序問題而失敗,出現“undeclared type”錯誤。

  • 在Swift 5模式下,返回Self的類方法不能再使用返回具體類型別(非final)的方法來覆蓋。這類程式碼不是型別安全的,需要將它們改掉。

例如:

class Base {     class func factory() -\u0026gt; Self { /*...*/ }} class Derived: Base {    class override func factory() -\u0026gt; Derived { /*...*/ } } 
  • 在Swift 5模式下,現在明確禁止宣告與巢狀型別同名的靜態屬性,而之前可以在泛型型別的擴充套件中進行這樣的宣告。

例如:

struct Foo\u0026lt;T\u0026gt; {}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模式相同的行為。

例如:

func foo(_ fn: @autoclosure () -\u0026gt; Int) {}func bar(_ fn: @autoclosure () -\u0026gt; Int) {    foo(fn) // Incorrect, `fn` can’t be forwarded and has to be called.    foo(fn()) // OK} 
  • 現在完全支援複雜的遞迴型別定義,包括之前在執行時會導致死鎖的類和泛型。

  • 在Swift 5模式下,在將Optional值轉換為通用佔位符型別時,編譯器在展開值時會更加保守。這種轉換結果現在更接近於非通用上下文中獲得的結果。

例如:

func forceCast\u0026lt;U\u0026gt;(_ value: Any?, to type: U.Type) -\u0026gt; U {    return value as! U } let value: Any? = 42print(forceCast(value, to: Any.self))// Prints \u0026quot;Optional(42)\u0026quot;// (Prior to Swift 5, this would print \u0026quot;42\u0026quot;.)print(value as! Any)// Prints \u0026quot;Optional(42)\u0026quot;
  • 協議現在可以將符合型別限定為給定類的子類。支援兩種等效形式:
protocol MyView: UIView { /*...*/ }protocol MyView where Self: UIView { /*...*/ } 

Swift 4.2接受了第二種形式,但還沒有完全實現,在編譯時或執行時偶爾會發生崩潰。

  • 在Swift 5模式下,當在自己的didSet或willSet observer中設定屬性時,observer現在只在self上設定屬性(不管是隱式的還是顯式的)時才會避免被遞迴呼叫。

例如:

class Node {    var children = Node     var depth: Int = 0 {        didSet {             if depth \u0026lt; 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            }         }    }}
  • 如果你使用#sourceLocation將生成檔案中的行對映回原始碼,那麼診斷資訊將顯示在原始檔中而不是生成檔案中。

  • 使用泛型型別別名作為@objc方法的引數或返回型別不會再生成無效的Objective-C標頭。

英文原文:
https://developer.apple.com/documentation/xcode_release_notes/xcode_10_2_beta_release_notes/swift_5_release_notes_for_xcode_10_2_beta?language=objc

更多內容,請關注前端之巔。

\"\"

相關文章