Swift5 新特性

在 Xcode10.2 版本中,可以使用 Swift5.0 進行開發

Swift 5 運行時對命令行工具的支持

Swift 命令行工具從 Xcode 10.2 開始需要依賴於 macOS 中的Swift庫。這些庫將從 macOS Mojave 10.14.3 開始默認包含在 macOS 中。在 macOS Mojave 10.14.2 和更早的版本中,可以選擇從 More Downloads for Apple Developers 下載 Swift 命令行工具所需要的運行時庫。如果你安裝了 beta 版本,需要將其替換成正式版本。只有在 Swift 命令行工具中才需要這個包,而並不適用於具有圖形用戶界面的應用程序。

App 瘦身(App Thinning)

新特性

Swift 不再包含 Swift 標準庫的動態鏈接,並且會在 iOS 12.2, watchOS 5.2, and tvOS 12.2 中內置 Swift SDK 。從而 Swift apps 在你上傳 App Store ,發佈 TestFlight 測試,和本地打包時會變得更小。

想要比較應用在 iOS 12.2 和 iOS 12.1 之前版本瘦身的大小的區別,你可以把應用的deployment target 設為 iOS 12.1 或之前的版本,然後將scheme 設為 Generic iOS Device 生成一個歸檔文件。然後用選擇 Archives organizer 中的發佈應用中的 Development distribution。確定在 App Thinning 的下拉菜單中選中某個特定的設備如:iPhone XS 。當發佈完成以後,在剛剛創建的文件夾中打開 App Thinning Size Report ,可以看到 iOS 12.2 的安裝包要比 iOS 12.1 或者更早的版本要小。準確的大小差別取決於你的 app 使用系統框架的數量。

Swift 語言特性(Swift Language)

新特性:

字符串的聲明,現在可以使用更好用的分隔符了。當使用(#)號來包住一個字符串的時候,可以使用多個(#)號來分割純字符和變量聲明。使用增強的分隔符,可以避免寫得很混亂的字符串轉義聲明。

print(#"

如果聲明的類型與標準庫中的類型具有相同的名稱,則會覆蓋了標準庫中的類型聲明。

例如,在模塊 Foo 中聲明瞭一個類型,名字是 Result

// Module `Foo`.
public enum Result {
case value(T)
case error(Error)
}

這樣在任何使用了 Foo 模塊的代碼中,Result 類型都將被認為是 Foo.Result:

import Foo
func doSomething() -> Result { /* … */ }

那麼如果真的想要引用標準庫中的 Result 類型,那麼必須加上顯式的聲明:

import Foo
func useStandardLibraryResult() -> Swift.Result { /* … */ }

如果一個變量聲明為@dynamicCallable的話,這樣可以使用一個函數調用的語法糖,這個語法糖主要的使用場景是動態語言互操作

例如:

@dynamicCallable struct ToyCallable {
func dynamicallyCall(withArguments: [Int]) {}
func dynamicallyCall(withKeywordArguments: KeyValuePairs<string>) {}
}
let x = ToyCallable()
x(1, 2, 3)
// 上面的代碼實際上是一個語法糖,如果不用語法糖的話,代碼會像這樣子寫 `x.dynamicallyCall(withArguments: [1, 2, 3])`

x(label: 1, 2)
// 上面的代碼實際上是一個語法糖,如果不用語法糖的話,代碼會像這樣子寫 `x.dynamicallyCall(withKeywordArguments: ["label": 1, "": 2])`

/<string>

標識鍵路徑(Key paths)功能現在支持一個特殊的標記方式(\.self),WritableKeyPath 指代了整個輸入值

let id = \Int.self
var x = 2
print(x[keyPath: id]) // Prints "2"
x[keyPath: id] = 3
print(x[keyPath: id]) // Prints "3"

在 Swift5 之前,可以編寫一個帶有可變參數的枚舉體:

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

這不是一個特意支持的特性,現在會產生編譯錯誤了。相對地,你可以用數組的聲明來代替可變參數聲明:

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

在 Swift5 下,try?如果用在可選類型上的話,就算是多層使用,也不會導致返回值是一個多層嵌套的可選類型了。

如果一個類型T符合 Initialization with Literals 中的一個協議,例如 ExpressibleByIntegerLiteral ,並且是一個標量表達式的話,那麼將不需要一直使用T用在泛型協議聲明中了

例如:現在可以這樣子寫一個變量表達式

UInt64(0xffff_ffff_ffff_ffff) 
  • 在之前的版本的話將會導致 Int 溢出錯誤。(SE-0213)(17088188)
  • 字符串插入大幅提高了性能表現。(SE-0228)(43621912)
  • 一箇舊的協議_ExpressibleByStringInterpolation被移除了。如果你希望繼續使用這個協議的話,你可以通過條件編譯選項來實現新老闆本的兼容。
  • 例如:
#if compiler(<5)
extension MyType: _ExpressibleByStringInterpolation { /*...*/ }

#else
extension MyType: ExpressibleByStringInterpolation { /*...*/ }
#endif

Swift 標準庫(Swift Standard Library)

新特性

  • 標準庫現在包括 Result 枚舉 Result.success(_:) 和 Result.failure(_:) 。在do-catch 語句和 try 表達式不能使用的情況下(例如在使用可能失敗的異步api時),使用 Result 手動傳遞和處理錯誤。
  • 作為添加的一部分,Error 協議的自我一致性,這使得在通用上下文中處理錯誤更加容易。(SE-0235)(21200405)
  • SIMD 類型和基本操作符現在在標準庫中定義。在 simd 框架提供的類型中, float2 和 float3,現在是新標準庫類型的類型別名。
  • SIMD 類型是標量元素類型上的泛型。例如,舊的 float3 類型是 SIMD3<float> 的類型別名。任何符合 SIMDScalar 協議的類型都可以用作 SIMD 向量的標量類型,但是有效的向量化依賴於為相關的 SIMDStorage 類型選擇一個良好的數據佈局並進行有效的下標操作。/<float>
  • 大多數使用 simd 類型的現有代碼可以繼續使用新的泛型 simd 類型,但是需要注意一些更改。
  • 新類型增加了一些新的一致性; SIMD 向量現在是 Hashable 、Equatable 和 Codable。這可能允許您刪除在您自己的代碼中提供這些一致性的一些現有擴展。
  • 為提供向量標量算法而重載的運算符集得到了極大的擴展。這使得編寫一些東西變得更容易,但是在某些情況下會給 typechecker 帶來歧義,並且可能需要分解一些表達式或使用顯式類型進行註釋。
  • 由於現在的類型是泛型而不是具體的,如果您已經在 simd 框架類型上定義了自己的協議,那麼可能有必要重構一致性,因為 Swift 泛型類型不能對協議有多個條件一致性。這種情況相對比較少見,但通常需要重構如下代碼:
protocol MyVectorProtocol { /* ... */ }
extension float2: MyVectorProtocol { /* ... */ }
extension double2: MyVectorProtocol { /* ... */ }
  • 要改為使用以下結構:
protocol MySIMDScalarProtocol: SIMDScalar { /* ... */ }
extension SIMD2 where Scalar: MySIMDScalarProtocol { /* ... */ }
// Or even:
protocol MySIMDScalarProtocol: SIMDScalar { /* ... */ }
extension SIMD where Scalar: MySIMDScalarProtocol { /* ... */ }
  • 這種更改通常允許您刪除許多冗餘實現,但它要求您定義任何必要的實現 Hook,這些 Hook 引用 Darwin 系統上標量類型的C頭文件中的具體函數。(SE-0229)(17045503)
  • Set 和 Dictionary 現在為每個新創建的實例使用不同的散列種子。因此,在 Set 和 Dictionary 中,元素的順序每次都會改變:
let a: Set = [1, 2, 3, 4, 5]
let b: Set = [1, 2, 3, 4, 5]
a == b // true
print(a) // [1, 4, 3, 2, 5]
print(b) // [4, 2, 5, 1, 3]
  • 現有的代碼錯誤地假設兩個不相關但相等的集合或字典將以相同的順序包含元素,這在 Swift 5 中更容易產生錯誤的結果。儘管元素順序在不同的 Set 或 Dictionary 實例之間並不穩定,但是在同一個實例上的多次迭代之間,順序不會發生變化。除了強調這些集合不能保證一致的元素順序外,此更改還修復了大量操作的情況。例如:union(_:) 二次性能。(44760778)
  • 為了防止 Cocoa 對象的不一致哈希,NSObject 上的 hashValue 屬性不再是可重寫的。在 Swift 4.2 廢棄重寫 hashValue。要在 NSObject 子類中重寫屬性 hash 來自定義哈希值。下面展示一個例子:
class Person: NSObject {
let name: String
init(name: String) {
self.name = name
super.init()
}
override func isEqual(_ other: Any?) -> Bool {
guard let other = other as? Person else { return false }
return other.name == self.name
}
override var hash: Int {
var hasher = Hasher()

hasher.combine(name)
return hasher.finalize()
}
}
  • 哈希和相等判斷是相輔相成。如果重寫 hash,還需要覆蓋 isEqual(_:),反之亦然。(42623458)
  • DictionaryLiteral 類型改名成 KeyValuePairs。 (SE-0214) (23435865)
  • Swift 字符串橋接到 Objective-C 可能適當地在 CFStringGetCStringPtr(::) 返回非 nil 值,而從 UTF8String 方法返回的指針則綁定到字符串的生命週期,而不是最內層的自動釋放池。正確的程序應該不會有任何問題,並可能看到顯著的加速。但是,它可能導致以前未測試的代碼運行,從而暴露潛在的bug;例如,如果對非 nil 值進行檢查,該分支可能從未在 Swift 5 之前被執行過。(26236614)
  • Sequence 協議不再具有 SubSequence 關聯類型。以前返回子序列的 Sequence 上的方法現在返回具體類型。例如,suffix(_:) 現在返回一個數組。(45761817)
  • 使用子序列的序列上的擴展應該修改為類似地使用具體類型,或者修改為 Collection 上的擴展(如果子序列仍然可用)。
  • 例如:
extension Sequence {
func dropTwo() -> SubSequence {

return self.dropFirst(2)
}
}
  • 變為:
extension Sequence {
func dropTwo() -> DropFirstSequence<self> {
return self.dropFirst(2)
}
}
/<self>
  • 或者:
extension Collection {
func dropTwo() -> SubSequence {
return self.dropFirst(2)
}
}
  • String 結構的本地編碼從 UTF-16 轉換為 UTF-8,這可能會提高 String.UTF8View 相對 String.UTF16View 的性能。考慮重新評估使用 String.UTF16View 來調優性能的代碼。(42339222)

已知的問題

  • Xcode 10.2 beta 版本 Sequence 協議中增加的 count(where:) 方法已經被移除。(47549309) **解決方案:**使用 reduce(::) 可以高效率地計算與謂詞匹配的出現次數:
let occurrences = sequence.reduce(0) { predicate($1) ? $0 + 1 : $0 }

已解決的問題

  • 可以按預期在字符串上設置 utf8 屬性。(47864538)
  • 傳遞 null UnsafeBufferPointer 給 String 結構體的 init(decoding:as:) 初始化方法現在可以正常的返回空字符串。 (47864610)

Swift 包管理(Swift Package Manager)

新特性

  • 當使用 Swift 5 Package.swift tools-version 時,Targets 可以聲明一些常用的、特定於目標的構建設置。還可以根據平臺和構建配置對新設置進行條件設置。所包含的構建設置支持 Swift 和 C 語言定義、C 語言頭文件搜索路徑、鏈接庫和鏈接 framework。(SE-0238)(23270646)
  • 當使用 Swift 5 Package.swift tools-version 時,可以指定最低部署版本。如果包的任何包依賴項指定的最小部署目標大於包本身的最小部署目標,則構建會出現錯誤。(SE-0236) (28253354)
  • 一個新的依賴項鏡像特性允許頂級包覆蓋依賴項url。(SE-0219)(42511642)
  • 使用以下命令設置鏡像:
$ swift package config set-mirror \
--package-url <original> --mirror-url <mirror>
/<mirror>/<original>
  • swift test 命令可以以一種標準格式生成代碼覆蓋率數據,這種格式適合使用標記 --enable-code-coverage 的其他代碼覆蓋率工具使用。生成的代碼覆蓋率數據在<build-dir>/<configuration>/codecov 中可用。(44567442)/<configuration>/<build-dir>
  • Swift 5 不再支持 Swift 3 Package.swift tools-version。仍然在 Swift 3 Package.swift tools-version 的包應該更新到一個新版本。(41974124)
  • 包管理器處理大型包的速度顯著加快了。(35596212)
  • Swift 包管理器有一個新的 --disable-automatic-resolution 標誌,它強制包解析在包失敗時失效。已解析項不再與 Package.swift manifest 文件中指定的依賴項版本兼容。這個特性對於持續集成系統檢查包 Package.resolved 是否過期非常有用。(45822895)
  • swift run 命令有一個新的 --repl 選項,該選項啟動 Swift REPL,支持導入目標包的庫。這使您可以輕鬆地從包目標測試API,而不需要構建調用該API的可執行文件。(44889181)
  • 有關使用Swift包管理器的更多信息,請訪問上 swift.org 的 Using the Package Manager。

Swift Compiler

新特性

  • 為了減少 Swift 元數據的佔用體積,Swift 中的 convenience initializers 現在將不會提前分配內存空間,除非其調用了在 Objective-C 中定義的 designated initializer。多數情況下,這不會對你的 app 產生任何影響。唯一例外是當你的 convenience Initializers 被 Objective-C 調用,同時,沒有調用自身暴露給 Objective-C 的 self.init ,那麼最初始分配的內存空間會在沒有調用任何 initializer 下被釋放掉。這可能會對使用 initializer 的人產生困擾,因為他們並沒有意識有 object replacement 的發生。一個例子是 initWithCoder::NSKeyedUnarchiver 如果調用了在 Swift 中定義的 initWithCoder: 並且保存了存在循環的對象圖,它最後的實現將會出錯。
  • 要避免這樣的問題,你需要保證 convenience initializers 不支持 object replacement,同時,確保最終調用的 initializers 暴露給 Objective-C,這可以是在 Objective-C 中定義 initializers,或是被標記了 @objc,亦或是他們被暴露給 Objective-C 的 initializers 覆寫了,也可以是他們遵從任何一個標記了 @objc 的協議。
  • 超過 16 字節對齊的 C 語言類型已不再被 Swift 支持。其實,之前版本 Swift 編譯器也沒有正確的處理這些類型過。(31411216)
  • 在 Swift 5 模式中,非 final 類的 convenience initializer 中 Self 的類型是動態的 Self 類型,而非具體類型了。(47323459)
  • 在 optimized build(-O 和 -Osize) 設置下,獨佔內存訪問會在 runtime 中默認強制啟用。若程序在 runtime 中違反獨佔內存訪問,則程序會產生一條診斷信息:“Simultaneous accesses to […], but modification requires exclusive access"。你可以通過用命令行標記 -enforce-exclusivity=unchecked 來禁用檢查,但是可能會導致產生未知的結果。Runtime 中違反獨佔內存訪問一般會是由同時對類屬性和全局變量(包括頂層代碼中的變量和被逃逸閉包持有的變量)同時訪問導致的。更多信息,請查看 Swift 5 Exclusivity Enforcement. (SR-7139) (37830912)
  • 移除對 Swift 3 的支持。現在編譯器支持的 -swift-version 是 4,4.2 和 5。
  • 在 Swift 5 中,在 Switch 中遍歷在 Objective-C 中定義的,或由系統框架定義的枚舉時,將被要求處理 unknown case。Unknown case 有可能來自於後來新增或在 Objective-C 的 .m 文件裡進行私有定義。之前,Objective-C 允許枚舉變量儲存符合定義類型的任何值。這些情況現在都可以用新的 @unknown default 來處理。不用擔心,編譯器依然會對漏掉的 case 進行警告。當然,普通的 default 也是可以處理的。
  • 如果你在 Objective-C 自定義了枚舉變量,也不需要客戶端對未知情況進行處理的話,那麼,你可以使用 NS_CLOSED_ENUM 宏來代替 NS_ENUM。 Swift 編譯器會識別這些宏,之後,就不會再要求對 default case 處理了。
  • 在 Swift 4 和 Swift 4.2 模式下,你還是可以使用 @unknown default 進行處理。如果你沒有添加 @unknown default, 而 runtime 中有未知 case 傳入,runtime 會捕獲到這樣的異常,這和 Xcode 10.1 中的 Swift 4.2 是一致的。(SE-0192) (39367045)
  • 現在 SourceKit 生成的 Swift Modules 的接口中,默認參數會被顯示出來,而非之前用 placeholder default 來顯示。(18675831)
  • unowned 和 unowned(unsafe) 變量現已支持 Optional 類型。(47326769)

Known Issues

  • Key Path 中如果引用另外一個 Swift Module 中 Protocol Extension 中的屬性,可能會導致編譯器奔潰。(48001932)
  • 暫時解決方案:在當前 Module 中定義中間屬性(wrapper property),在 key path 中引用這個中間屬性而非原有屬性。
  • 若啟用Thread Sanitizer,可能導致 Swift 編譯器在編譯過程中奔潰。
  • 暫時解決方案:在 Scheme Editor 的 Diagnostics 標籤頁下禁用 Thread Sanitizer。
  • 鏈接靜態 Swift 庫可能會導致創建的二進制程序中缺失類型元數據,原因是靜態庫中的定義元數據的 Object 文件被錯誤的當做未使用。
  • 這有可能導致一個 Swift runtime 錯誤,同時會有類似 “failed to demangle superclass of MyClass from mangled name ‘’” 的錯誤信息。
  • 暫時解決方案:如果你能重新編譯靜態庫,請嘗試啟用 whole module optimization 下進行編譯。 否則,可以考慮添加 -all_load 到客戶端二進制程序的鏈接器中來保證所有的 Object 文件都被鏈接入二進制程序。
  • self.init() 在 NSView 和 UIView 的子類的 designated initializers 未被禁止,即便 init() 在 NSView 和 UIView 中是 convenience initializer。使用 self.init() 可能會導致儲存屬性被多次初始化,最終可能導致內存洩漏或者是其他問題。(SR-9836) (47734208)
  • 暫時解決方案:使用 super.init(frame:) 來替代 self.init()

Resolved Issues

  • 除非你安裝 Swift 5 命令行工具包的運行時支持,否則 Swift 命令行項目將無法在 macOS 10.14.3 及更早版本上運行。 如果沒有該工具包,Swift 命令行項目會在啟動時因“dyld:Library not loaded” 錯誤而崩潰。(46824656)
  • 直接從命令行用 swiftc 編譯器鏈接一個 Swift 項目,現在可以在 macOS Mojave 10.14.4 上輸出正確的結果。(43616773)
  • 與 Xcode 10.1 相比,Xcode 10.2 中的編譯時間倒退現在在大多數情況下得到解決。 有些項目可能會繼續經歷小幅倒退;文件錯誤報告會記錄您遇到的案例。(47304789)
  • 即使引用了 UIAccessibility 結構體的成員或包含 NS_ERROR_ENUM 嵌套類型的其他類型,Swift 編譯器也會完成“Merge swiftmodule”構建步驟。(47152185)
  • 在某些上下文中允許使用單元素標記的元組表達式,如 (label:123),但通常會導致令人驚訝的,不一致的行為,這些行為在編譯器版本中會有所不同。 他們現在完全被禁止了。 在 Swift 3 中已經禁止使用單元素標記類型,如var x:(label:Int)。(SR-8109) (41474370)
  • 如果 KeyPath 字面量引用了在 Objective-C 中定義的屬性或者在 Swift 中使用 @objc 和動態修飾符定義的屬性,它 現在可以成功編譯並在運行時生成正確的哈希值或與其他 KeyPath 的相等比較。(47184763)
  • 擴展綁定現在支持嵌套類型的擴展,這些類型本身是在擴展內定義的。之前可能會因為聲明順序問題而失敗,出現“undeclared type”錯誤。(SR-631) (20337822)
  • In Swift 5 mode, inferred associated types are no longer exposed publicly when a public type conforms to a non-public protocol. Instead, they get the minimum possible access to be visible from both the protocol and the conforming type. For source compatibility, Swift 4 and 4.2 modes continue to expose inferred associated types as publicly as the enclosing type unless the inferred associated type is itself less public than the conforming type.
  • 在 Swift 5 模式下,當公共類型實現非公共協議時,推斷的關聯類型不再公開。 相反,它們獲得同時對協議和實現類型可見的最小訪問權限。對於版本兼容性,Swift 4 和 4.2 模式繼續將推斷的關聯類型公開為封閉類型,除非推斷的關聯類型本身不如實現類型開放。(46143405)
  • 在 Swift 5 模式下,返回 Self 的類方法不能再使用返回具體類類型(非 final)的方法來覆蓋。這類代碼不是類型安全的,需要將它們改掉。 (SR-695) (47322892)
  • 例如:
class Base { 
class func factory() -> Self { /*...*/ }
}
class Derived: Base {
class override func factory() -> Derived { /*...*/ }
}
  • 在 Swift 5 模式下,現在明確禁止聲明與嵌套類型同名的靜態屬性,而之前可以在泛型類型的擴展中進行這樣的聲明。(SR-7251) (47325738)
  • 例如:
struct Foo {}
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 }
}
  • 現在可以在子類中正確繼承具有可變參數的指定初始化器。(16331406)
  • 在 Swift 5 模式下,@autoclosure 參數不能再被轉發給另一個函數調用的 @autoclosure 參數。相反,你必須使用括號顯式調用函數值。調用將被包含在一個隱式閉包中,保證了與 Swift 4 模式相同的行為。(SR-5719) (37321597)
  • 例如:
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
}
  • 現在完全支持複雜的遞歸類型定義,包括之前在運行時會導致死鎖的類和泛型。(38890298)
  • 在 Swift 5 模式下,在將 Optional 值轉換為通用佔位符類型時,編譯器在展開值時會更加保守。這種轉換結果現在更接近於非通用上下文中獲得的結果。(SR-4248) (47326318)
  • 例如:
func forceCast(_ 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) (38077232)
  • Swift 4.2 接受了第二種形式,但還沒有完全實現,在編譯時或運行時偶爾會發生崩潰。
  • 在 Swift 5 模式下,當在自己的 didSet 或 willSet observer 中設置屬性時,observer 現在只在 self 上設置屬性(不管是隱式的還是顯式的)時才會避免被遞歸調用。(SR-419) (32334826)
  • 例如:
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
}
}
}
}
  • 如果你使用 #sourceLocation 將生成文件中的行映射回源代碼,那麼診斷信息將顯示在源文件中而不是生成文件中。(43647151)
  • 使用泛型類型別名作為 @objc 方法的參數或返回類型不會再生成無效的 Objective-C 標頭。(SR-8697) (43347303)

該文來自github的Swift 老司機活動中心

地址:https://github.com/SwiftOldDriver


分享到:


相關文章: