萬眾期待的 Swift 5 終於來了,蘋果爸爸答應的 ABI 穩定也終於來了。
小集新小夥伴 @NotFound-- 花時間將文件翻譯出來,供大家參考。翻譯不當之處,請及時留言指出,我們會持續更新。
App 瘦身
新特性
Swift 應用程式不再包含用於 Swift 標準庫的動態連結庫
和用於執行 iOS 12.2
,watchOS 5.2
和 tvOS 12.2
的裝置的構建變體中的 Swift SDK overlays
。因此,當為 TestFlight
進行測試部署時,或者在為本地開發分發瘦身應用的 archive
包時,Swift 應用程式可以更小。
要對比 iOS 12.2 和 iOS 12.1(或更早版本) 瘦身後 App 的檔案大小差異,可以設定 App 的 deployment target
為 iOS 12.1
或更早版本,設定 scheme set
為 Generic 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
的字面量,而不是使用一個協議的預設字面量型別的值來呼叫T
的initializer
。如,類似於
UInt64(0xffff_ffff_ffff_ffff)
這樣的表示式現在是有效的,則之前會由於整型字面量的預設型別是Int
,而導致溢位。(SE-0213) -
提高了
字串插值
操作的效能、清晰性和效率。(SE-0228)舊的
_ExpressibleByStringInterpolation
協議被刪除;如果您有使用此協議的程式碼,則需要做相應更新。您可以使用#if
條件判斷來區分Swift 4.2
和Swift 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
關聯型別。先前返回SubSequence
的Sequence
方法現在會返回具體型別。例如,suffix(_:)現在會返回一個Array
。(47323459)使用
SubSequence
的Sequence
擴充套件應該修改為類似地使用具體型別,或者修改為 Collection 的擴充套件,在Collection
中SubSequence
仍然可用。(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
設定。新設定也可以基於平臺和構建配置進行條件化處理。包含的構建設定支援Swift
和C
語言定義,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
標誌支援的值為4
、4.2
和5
。 -
在 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 4
和4.2
模式下,您仍然可以使用@unknown default
。如果省略@unknown default
,而又傳遞了一個未知的值,則程式在執行時丟擲異常,這與Xcode 10.1
中的Swift 4.2
上的行為是一致的。(SE-0192)(39367045) -
現在在
SourceKit
生成的 Swift 模組介面中會列印預設引數,而不僅僅是使用佔位符。 -
unowned
和unowned(unsafe)
型別的變數現在支援可選型別。
已知的問題
- 如果引用了
UIAccessibility
結構的任何成員,則 Swift 編譯器會在 “Merge swiftmodule
” 構建步驟中崩潰。構建日誌包含一條訊息:
Cross-reference to module 'UIKit'
... UIAccessibility
... in an extension in module 'UIKit'
... GuidedAccessError
複製程式碼
包含 NS_ERROR_ENUM
列舉的其他型別也可能出現此問題,但 UIAccessibility
是最常見的。(47152185)
解決方法:在 target
的 Build 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-C
的initializer
,或者是它滿足@objc
協議的要求。(46823518) -
如果一個
keypath
字面量引用了Objective-C
中定義的屬性,或者是在Swift
中使用@objc
和dynamic
修飾符定義的屬性,則編譯可能會失敗,並且報 “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 中,當在屬性自身的
didSet
或willSet
中設定屬性本身時,會避免遞迴呼叫(不論是隱式或顯式地設定自身的屬性)。(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,也歡迎加入我們的群組討論問題。可以公眾號留言 ios
、flutter
等關鍵詞獲取入群方式。