Swift 5.1 介紹

文章比較長(zhǎng),不想看完的可以看總結(jié)。

總結(jié)

知道 SwiftUI 的人,都會(huì)很想使用這個(gè)框架,但是由于只能在 iOS 13以及以后的系統(tǒng)使用,所以會(huì)很痛心。像我就覺得為什么 SwiftUI 不開源??!開源多好,將底層渲染替換一下,就能用 SwiftUI 在 Android 上編程了!對(duì) Flutter 也有沖擊。但是就像我在文中說的 Swift5.1 引入的二進(jìn)制庫穩(wěn)定導(dǎo)致,蘋果可以不開源我們就能使用 SwiftUI.framework。(好痛心??,寧愿不要二進(jìn)制庫穩(wěn)定!)

幸運(yùn)的是,SwiftUI 之所以能夠出現(xiàn),很大程度上得益于 Swift5.1 引入的新特性屬性包裝器(文中有介紹,但是坑爹的簡(jiǎn)書貌似不支持 Markdown 文內(nèi)鏈接)以及到目前為止官方都沒有披露的 @_functionBuilder。雖然由于需要支持 iOS 13 以前系統(tǒng)的原因不能使用 SwiftUI,但是只要我們使用 Swift5.1 就可以使用 Swift 的這兩個(gè)新特性。這樣一來,在 iOS 13 以前我們可以開發(fā)另一套界面描述 DSL,同時(shí)也能夠很容易支持?jǐn)?shù)據(jù)的雙向綁定,如此一來,從 Swift5.1 開始 iOS 開發(fā)人員編寫界面的方式會(huì)有很大的變化,從以前的命令式過渡到現(xiàn)在的聲明式。畢竟 SwiftUI 的最大討好點(diǎn)就是聲明式的界面編寫。(從這一點(diǎn)來說我反而比較擔(dān)心 SwiftUI 可能并不會(huì)被開發(fā)者青睞。因?yàn)楫吘共恢С?iOS 13 以前的系統(tǒng),而且使用 Swift5.1 也很容易的實(shí)現(xiàn)聲明式的 UI 框架,不過考慮到 SwiftUI 是跨 iOS 和 macOS 平臺(tái)的,所以,可能并不是我想的那么不被青睞)

最后一句總結(jié),由于屬性包裝器以及 @_functionBuilder 的存在,從 Swift5.1 開始,Swift 成為一門真正的支持高效 UI 編程的語言,一門高效的用于開發(fā) DSL 的語言,一門更加易用的語言!

前言

視頻地址:What's New in Swift
本文介紹視頻中的主要內(nèi)容。(以及夾帶一些私貨)

二進(jìn)制庫穩(wěn)定

ABI 穩(wěn)定

Swift 5 帶來了 ABI 的穩(wěn)定。但是用 Swift 寫的二進(jìn)制框架并沒有穩(wěn)定。

先說一下什么是 ABI(Application Binary Interface),打個(gè)比方,代碼中調(diào)用一個(gè)函數(shù),需要傳入幾個(gè)參數(shù),調(diào)用結(jié)束需要返回結(jié)果,編譯器如何將參數(shù)傳給被調(diào)用者,以及如何將結(jié)果返回給調(diào)用方,就是 ABI 涉及的內(nèi)容。ABI 不穩(wěn)定意味則,如何傳參數(shù)和返回參數(shù)還沒有最終確定(當(dāng)然這只是舉例,實(shí)際的編譯器實(shí)現(xiàn)很復(fù)雜,ABI 涉及很多方面)。ABI 不穩(wěn)定導(dǎo)致的結(jié)果就是,使用 Swift 5 以前的版本編寫 iOS app,Xcode 需要同時(shí)將 ABI 相關(guān)的 Swift 核心庫編譯到你的 app 中,直觀的感受就是你的 ipa 包變大了。

?? iOS 12.2 系統(tǒng)內(nèi)置 Swift 核心庫所以 Swift5 編寫的 app 不需要內(nèi)置 Swift 核心庫了,但是 Xcode 在打包的時(shí)候仍然會(huì)將 Swift5 核心庫集成到 ipa 里面,這樣 Swift5 寫的 app 就能運(yùn)行在 iOS12.2 之前系統(tǒng)。只是對(duì)于 iOS 12.2 及以后的系統(tǒng)安裝該 app 的時(shí)候,系統(tǒng)不需要下載 app 中 Swift 的核心庫了,減少了下載體積和安裝大小。(同時(shí) app 啟動(dòng)也變快了,因?yàn)橄到y(tǒng) dyld3 緩存了對(duì)應(yīng)的 Swift 核心庫,官方說能帶來 5% 的啟動(dòng)速度提升)

??? 問題來了,如果你用 Swift5.1 編寫的 app,安裝在 iOS12.2 上面,需要下載并且安裝 Swift5.1 對(duì)應(yīng)的核心庫嗎?

ABI 穩(wěn)定之后就是模塊穩(wěn)定了。

模塊穩(wěn)定

模塊穩(wěn)定指的是,用 Swift5.1 以前編寫的模塊,如果想要提供給第三方的話,需要把這個(gè)模塊的代碼也提供第三方,這樣他們才能使用這個(gè)模塊。當(dāng)然這對(duì)于某些模塊的開發(fā)者來說是不可接受的(畢竟是把模塊的源代碼給別人了)。模塊庫穩(wěn)定指的是,用 Swift5.1,庫開發(fā)者不需要提供源代碼給第三方了,只需要提供一個(gè)類似頭文件的東西給第三方,第三方就可以使用了。

順便簡(jiǎn)單說一下 Swift 是如何使用模塊。如下圖所示:

Swift5及以前.png

可以知道 Swift5.1 以前,在 Foo.swift 中 import MyFramework 的話,Swift 會(huì)自動(dòng)在 Foo.swift 中導(dǎo)入 MyFramework 的“頭文件”亦即 swiftmodule 文件,這個(gè)文件是編譯器在編譯 MyFramework 的時(shí)候生成的。如此一來,就可以在 Foo.swift 中任意使用 MyFramework 中的類、結(jié)構(gòu)體、函數(shù)等了,比如圖中的 doSomething 方法。

Swift5.1 也差不多,如下圖:

Swift5.1

只是將 swiftmodule 文件改成了 swiftinterface 文件。當(dāng)然 swiftinterface 對(duì)于庫開發(fā)者來說應(yīng)該是可以編輯的。庫開發(fā)者需要將這個(gè)文件和二進(jìn)制文件提供給第三方。

ABI 和模塊的穩(wěn)定意味者二進(jìn)制庫的穩(wěn)定。

對(duì)于更多關(guān)于 Swift 庫穩(wěn)定相關(guān)的內(nèi)容可以查看本文引用中的鏈接。

??? 問題來了, 如果第三方用的是 Swift5.1 以前的版本開發(fā)的話,那么庫開發(fā)者(使用 Swift5.1)是否需要源碼呢?

Swift5.1 的優(yōu)化

  1. 編譯出來的二進(jìn)制更小了,通常是 10% 的減少,如果開啟 ‘Optimize for Size‘ 就是 15% 的減少。
  2. Swift 和 ObjC 的橋接更快了
    1. NSString 和 String 橋接有15x 的提升
    2. NSDictionary 和 Dictionary 有 1.6x 的提升
    3. 大小比較小的 String 的優(yōu)化

這些對(duì) String 和 Dictionary 的優(yōu)化使得 SwiftNIO 請(qǐng)求處理速度提升了 20%。

SwiftNIO 是 Swift 官方出的跨平臺(tái)異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用框架

Swift 開源相關(guān)

  1. Swift 官方 docker 鏡像
  2. Swift 的 SourcKit (用于代碼補(bǔ)全、高亮、重構(gòu)以及符號(hào)跳轉(zhuǎn)到定義等編寫 Swift 代碼相關(guān)的編輯器功能)的壓力測(cè)試(為什么壓力測(cè)試,用過 Xcode 的都知道 Xcode 的 Swift 編輯器有多爛 ??)
  3. LSP(Language Server Protocol)

LSP 的作用如下所示,簡(jiǎn)單的說就是代碼編輯器有很多比如 Xcode 的 Source Editor、VSCode、Vim 等等,每個(gè)編輯器都要支持各種類型代碼(比如 Java、Python、Swift 等)編輯相關(guān)的操作(高亮、補(bǔ)全等)。

由于 SourceKit 定義的內(nèi)容被 Xcode 使用,不夠通用,沒法直接給 VSCode 等第三方編輯器使用,所以需要使用 LSP 協(xié)議來做橋接(計(jì)算機(jī)領(lǐng)域有句名言,所有復(fù)雜問題都可以引入另一個(gè)中間層來解決)。
LSP

Swift 新特性

所有的新特性都可以在 Swift Evolution 中找到。

這里介紹比較重要的一些內(nèi)容。

單行表達(dá)式隱式返回

這個(gè)很簡(jiǎn)單。在以前需要寫這種代碼:

// Implicit return from single expressions
// Swift Evolution: SE-0255
struct Rectangle {
 var width = 0.0, height = 0.0
 var area: Double { return width * height }.
}.

現(xiàn)在 Swift5.1 可以這樣寫了

// Implicit return from single expressions
// Swift Evolution: SE-0255
struct Rectangle {
 var width = 0.0, height = 0.0
 var area: Double { width * height }.
}.

結(jié)構(gòu)體初始化

以前需要這樣初始化結(jié)構(gòu)體

// Synthesized default values for the memberwise initializer
// Proposal and implementation by an open source contributor Alejandro Alonso
// Swift Evolution: SE-0242
struct Dog {
 var name = "Generic dog name"
 var age = 0
}
let boltNewborn = Dog()
let daisyNewborn = Dog(name: "Daisy", age: 0) 
let benjiNewborn = Dog(name: "Benji") // 編譯錯(cuò)誤?

現(xiàn)在最后一句也能編譯通過了。

字符串插值

字符串插值指的是:

let quantity = 10
label.text = "You have \(quantity) apples"

Swift 5.1 以前,不能這樣寫

let quantity = 10
label.text = NSLocalizedString(
 "You have \(quantity) apples"
,
 comment: "Number of apples"
) // 編譯錯(cuò)誤?

需要改成這樣子:

let quantity = 10
let formatString = NSLocalizedString(
 "You have %lld apples",
 comment: "Number of apples"
)
label.text = String(format: formatString, quantity)

Swift5.1 可以這樣寫了:

let quantity = 10
return Text(
 "You have \(quantity) apples"
,
 comment: "Number of apples"
)

其中 Text 來自 SwiftUI 框架,定義如下:

// In SwiftUI.framework
public struct Text {
 public init(
 _ key: LocalizedStringKey,
 tableName: String? = nil,
 bundle: Bundle? = nil,
 comment: StaticString? = nil
 )
}

Swift 會(huì)將上述代碼轉(zhuǎn)換成這樣:

let quantity = 10
return Text(
 "You have \(quantity) apples",
 comment: "Number of apples"
)

// Generated by the Swift compiler
var builder = LocalizedStringKey.StringInterpolation(
 literalCapacity: 16, interpolationCount: 1
).
builder.appendLiteral("You have ")
builder.appendInterpolation(quantity)
builder.appendLiteral(" apples")
LocalizedStringKey(stringInterpolation: builder)

這些得益于 ExpressibleByStringInterpolation 這個(gè)新的協(xié)議

返回值類型抽象化

為什么要抽象化返回值類型呢?因?yàn)椴幌氡┞秾?shí)現(xiàn)細(xì)節(jié),但是在 Swift5.1 以前某些情況下有問題。

如下類型:

// Shapes Example
protocol Shape { /* ... */ }
struct Square: Shape { /* ... */ }
struct Circle: Shape { /* ... */ }
struct Oval: Shape { /* ... */ }
struct Union<A: Shape, B: Shape>: Shape { /* ... */ }
struct Transformed<S: Shape >: Shape { /* ... */ } 

下面這種情況還好:

// API returns different types conforming to the same protocol
// Use Protocol
struct FaceShape {
 …
 var shape: Shape {
   switch faceType {
     case .round:
       return Circle()
     case .square:
       return Square()
     case .diamond:
       return Transformed(Square(), by: .fortyFiveDegrees))
     default:
       return Oval()
     }
   }
}

這種情況:

// API returns the same type but is leaking implementation details
struct EightPointedStar {
 …
 var shape: Union<Square, Transformed<Square>> {
   return Union(Square(), Transformed(Square(), by: .fortyFiveDegrees))
   }
}

就不能寫成下面這樣了:

// API returns the same type but is leaking implementation details
// Protocol type?
struct EightPointedStar {
 …
   var shape: Shape {
     return Union(Square(), Transformed(Square(), by: .fortyFiveDegrees))
   }
}

為什么呢?因?yàn)槿鄙倭祟愋托畔?。而?Swift 的泛型系統(tǒng)支持的不夠好,同時(shí)阻礙了 Swift 編譯器的優(yōu)化。

在 Swift5.1 中可以這樣寫了(引入了 some 關(guān)鍵字)

// API returns the same type but is leaking implementation details
// Use Opaque Result Types
struct EightPointedStar {
 …
 var shape: some Shape {
   return Union(Square(), Transformed(Square(), by: .fortyFiveDegrees))
 }
}

但是還是不能這樣寫:

// Opaque Result Types
// Compiler enforces that the same type is returned from the implementation
struct EightPointedStar {
 …
 var shape: some Shape {
   if symmetrical {
     return Union(Square(), Transformed(Square(), by: .fortyFiveDegrees))
   } else {
     return Transformed(Square(), by: .twentyDegrees)
   }
 }
}

屬性包裝

很重要!?。?/strong>

直接上代碼,在以前,會(huì)有這樣的代碼:

static var usesTouchID: Bool {
   get {
     return UserDefaults.standard.bool(forKey: "USES_TOUCH_ID")
   }
   set {
     UserDefaults.standard.set(newValue, forKey: "USES_TOUCH_ID")
   }
}

static var isLoggedIn: Bool {
   get {
     return UserDefaults.standard.bool(forKey: "LOGGED_IN")
   }
   set {
     UserDefaults.standard.set(newValue, forKey: "LOGGED_IN")
   }
}

現(xiàn)在,使用 Swift5.1 可以這樣了:

// Using UserDefault property wrapper to declare and access properties
@UserDefault("USES_TOUCH_ID", defaultValue: false)
static var usesTouchID: Bool
@UserDefault("LOGGED_IN", defaultValue: false)
static var isLoggedIn: Bool

if !isLoggedIn && usesTouchID {
 !authenticateWithTouchID()
}

問題來了:@UserDefault 哪里來的?UserDefault 其實(shí)是一個(gè)泛型結(jié)構(gòu)體,如下所示:

// The purpose of property wrappers is to wrap a property, specify its access patterns
@propertyWrapper
struct UserDefault<T> {
 let key: String
 let defaultValue: T
 init(_ key: String, defaultValue: T) {
 …
   UserDefaults.standard.register(defaults: [key: defaultValue])
 }

 var value: T {
   get {
     return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
   }
   set {
     UserDefaults.standard.set(newValue, forKey: key)
   }
 }
}

定義以上內(nèi)容之后,我們就可以使用 @UserDefault 這種方式了。

熟悉 Java 的人會(huì)說這是注解,熟悉 Python 或者 JavaScript 的會(huì)說這是裝飾器,不管如何,兩者有異曲同工的效果!

我以前寫過一篇文章介紹這個(gè),感興趣的可以去看看:Swift 中類似 Java 的注解:Attribute

DSL(Domain Specific Languages,領(lǐng)域特定語言)

視頻中 Swift 開發(fā)者介紹了 Swift 就有了一項(xiàng)新的能力: 很容易就能支持 DSL!

DSL 是一種描述語言,描述一種特定的內(nèi)容,比如界面的結(jié)構(gòu)等等。這種描述語言需要編程語言作為載體(或者其他,但是編程語言是其中最好的一個(gè)載體)。對(duì) DSL 不熟悉的可以查看文末詳細(xì)鏈接。

DSL 簡(jiǎn)單的比喻就是,將原本字符串描述的內(nèi)容用編程語言描述出來。比如 XML 這種格式的字符串可以描述很多,比如界面和數(shù)據(jù)結(jié)構(gòu)等,但是為什么一定要編程語言描述出來呢?

  1. 因?yàn)榫幊陶Z言有編譯器的幫助可以幫助你寫出合法、有效的描述,并且支持編譯優(yōu)化,
  2. 編程語言可以被執(zhí)行,所以if-else 語句,while、for 語句,等等各種表達(dá)式以及對(duì)應(yīng)編程語言的所有特性都能使用,幫助你構(gòu)建靈活的描述
  3. 編輯器在編譯器的幫助下可以支持代碼高亮、補(bǔ)全等等,加快你的開發(fā)。
  4. 編程語言是嚴(yán)謹(jǐn)?shù)木_的,而且開發(fā)人員喜歡寫代碼!而不是寫各類配置或者字符串描述,想象一下,你寫字會(huì)有錯(cuò)別字這種情況。

接下來介紹 Swift 具體是如何支持 DSL 的。

我們現(xiàn)在可以這樣子用 Swift5.1 寫代碼描述一個(gè) HTML 文檔了:

html {
 head {
   title("\(name)'s WWDC19 Blog")
 }
 body {
   h2 { "Welcome to \(name)'s WWDC19 Blog!" }

   br()

   "Check out the talk schedule and latest news from "

   a {
   "the source"
   }.href("https://developer.apple.com/wwdc19/")

   if !loggedIn {
     button {
       "Log in for comments"
     }
     .onclick("triggerLogin()")
   }
 }..
}..

如何實(shí)現(xiàn)的呢?

如下所示:

// Functions that construct the HTML objects
public func html(@HTMLBuilder content: () -> HTML) -> HTML { … }
public func head(@HTMLBuilder content: () -> HTML) -> HTML { … }
public func body(@HTMLBuilder content: () -> HTML) -> HTML { … }

可見,這些函數(shù)定義使用了上一節(jié)說的屬性包裝器,也就是 HTMLBuilder,有了這個(gè)包裝器,下面的代碼:

head {
 meta().charset("UTF-8")
 if cond {
   title("Title 1")
 } else {
   title("Title 2")
 }
}

在運(yùn)行時(shí)候的效果類型下面這樣子??:

head {
 let a: HTML = meta().charset("UTF-8")
 let d: HTML
 if cond {
   let b: HTML = title("Title 1")
   d = HTMLBuilder.buildEither(first: b)
 } else {
   let c: HTML = title("Title 2")
   d = HTMLBuilder.buildEither(second: c)
 }
 return HTMLBuilder.buildBlock(a, d)
}

嗯,如果關(guān)注 WWDC19 的話你會(huì)發(fā)現(xiàn),iOS13 引入的新的 UI 編程框架 SwiftUI.framework 這個(gè)框架就是像上面這樣被實(shí)現(xiàn)的??!

不過可惜的是 Swift 支持的 @_functionBuilder 在會(huì)上并沒有透露出來,這個(gè)東西也是筆者在網(wǎng)上找到的,可以查看本文引用中的 Swift functionBuilder

引用

  1. Apple 介紹 dyld3
  2. Swift 官方博客關(guān)于 ABI 穩(wěn)定的介紹
  3. Swift UTF8 String
  4. SwiftNIO
  5. Swift SourcKit 壓力測(cè)試
  6. LSP
  7. sourcekit-lsp
  8. swift-evolution/
  9. ExpressibleByStringInterpolation
  10. Opaque Result Types
  11. New Design for String Interpolation
  12. SIMD — A Better API for Vector Programming
  13. Property Wrapper Types
  14. Domain Specific Languages
  15. Swift 中類似 Java 的注解:Attribute
  16. Swift functionBuilder
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容