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

近日,蘋果開發者博客更新了一篇關於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<String, Int>) {}
}

let x = ToyCallable()

x(1, 2, 3)
// Desugars to `x.dynamicallyCall(withArguments: [1, 2, 3])`

x(label: 1, 2)
// Desugars to `x.dynamicallyCall(withKeywordArguments: ["label": 1, "": 2])`

現在支持標識KeyPath(.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"

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

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]) 
} 

在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(<5)
extension MyType: _ExpressibleByStringInterpolation { /*...*/ }
#else
extension 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() -> 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.UTF8View的性能(相對於String.UTF16View)。

Swift包管理器

  • 現在,在使用Swift 5 Package.swift工具版本時,可以聲明一些常用的特定於目標的構建設置。新的設置也可以基於平臺和構建配置進行條件化。構建設置支持Swift和C語言定義、C語言頭文件搜索路徑、鏈接庫和鏈接框架。

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

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

可以使用以下命令設置鏡像:

$ swift package config set-mirror \
--package-url <original URL> --mirror-url <mirror URL>
  • 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() -> Self { /*...*/ }
} 

class Derived: Base {
    class override func factory() -> Derived { /*...*/ } 
} 
  • 在Swift 5模式下,現在明確禁止聲明與嵌套類型同名的靜態屬性,而之前可以在泛型類型的擴展中進行這樣的聲明。

例如:

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模式相同的行爲。

例如:

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模式下,在將Optional值轉換爲通用佔位符類型時,編譯器在展開值時會更加保守。這種轉換結果現在更接近於非通用上下文中獲得的結果。

例如:

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接受了第二種形式,但還沒有完全實現,在編譯時或運行時偶爾會發生崩潰。

  • 在Swift 5模式下,當在自己的didSet或willSet observer中設置屬性時,observer現在只在self上設置屬性(不管是隱式的還是顯式的)時纔會避免被遞歸調用。

例如:

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將生成文件中的行映射回源代碼,那麼診斷信息將顯示在源文件中而不是生成文件中。

  • 使用泛型類型別名作爲@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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章