Swift5.5 新特性

  1. Async/await
  2. 是個啥
    一言以蔽之, 以前需要用閉包回調(diào)來寫的代碼, 我們現(xiàn)在可以用async/await來寫, 這讓我們可以拋棄復(fù)雜的閉包嵌套代碼, 極大的簡化了代碼, 提升可讀性
    舉個??
    我們先查詢歷史天氣, 再計算出平均溫度, 最后上傳
func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
    DispatchQueue.global().async {
        let results = (1...100_000).map { _ in Double.random(in: -10...30) }
        completion(results)
    }
}

func calculateAverageTemperature(for records: [Double], completion: @escaping (Double) -> Void) {
    DispatchQueue.global().async {
        let total = records.reduce(0, +)
        let average = total / Double(records.count)
        completion(average)
    }
}

func upload(result: Double, completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        completion("OK")
    }
}

調(diào)用的時候

fetchWeatherHistory { records in
    calculateAverageTemperature(for: records) { average in
        upload(result: average) { response in
            print("Server response: \(response)")
        }
    }
}

可以發(fā)現(xiàn), 無論是寫起來還是閱讀都非常hard, 尤其對新手及其不友好
那么用了async/await之后呢

func fetchWeatherHistory() async -> [Double] {
    (1...100_000).map { _ in Double.random(in: -10...30) }
}

func calculateAverageTemperature(for records: [Double]) async -> Double {
    let total = records.reduce(0, +)
    let average = total / Double(records.count)
    return average
}

func upload(result: Double) async -> String {
    "OK"
}

可以看到, 非常清晰, 可讀性非常高

  1. async/await 也支持 try/catch
    for example:
enum UserError: Error {
    case invalidCount, dataTooLong
}

func fetchUsers(count: Int) async throws -> [String] {
    if count > 3 {
        // Don't attempt to fetch too many users
        throw UserError.invalidCount
    }

    // Complex networking code here; we'll just send back up to `count` users
    return Array(["Antoni", "Karamo", "Tan"].prefix(count))
}

func save(users: [String]) async throws -> String {
    let savedUsers = users.joined(separator: ",")

    if savedUsers.count > 32 {
        throw UserError.dataTooLong
    } else {
        // Actual saving code would go here
        return "Saved \(savedUsers)!"
    }
}

使用

func updateUsers() async {
    do {
        let users = try await fetchUsers(count: 3)
        let result = try await save(users: users)
        print(result)
    } catch {
        print("Oops!")
    }
}
  1. 只讀屬性里也可以用
enum FileError: Error {
    case missing, unreadable
}

struct BundleFile {
    let filename: String

    var contents: String {
        get async throws {
            guard let url = Bundle.main.url(forResource: filename, withExtension: nil) else {
                throw FileError.missing
            }

            do {
                return try String(contentsOf: url)
            } catch {
                throw FileError.unreadable
            }
        }
    }
}

func printHighScores() async throws {
    let file = BundleFile(filename: "highscores")
    try await print(file.contents)
}

  1. async let的使用
struct UserData {
    let username: String
    let friends: [String]
    let highScores: [Int]
}

func getUser() async -> String {
    "Taylor Swift"
}

func getHighScores() async -> [Int] {
    [42, 23, 16, 15, 8, 4]
}

func getFriends() async -> [String] {
    ["Eric", "Maeve", "Otis"]
}

如果想通過這三個屬性構(gòu)造一個User對象, async let 將是最簡單的方法 -- 每個方法都是異步的, 等待這三個方法全部執(zhí)行完, 才會去構(gòu)建新對象

func printUserDetails() async {
    async let username = getUser()
    async let scores = getHighScores()
    async let friends = getFriends()

    let user = await UserData(name: username, friends: friends, highScores: scores)
    print("Hello, my name is \(user.name), and I have \(user.friends.count) friends!")
}

重點: async let 必須在聲明為async的context中, 如果缺少async標(biāo)記, 那么async let將不會等待結(jié)果產(chǎn)生, 直到離開當(dāng)前這個作用域

在那些會拋出異常的方法中, 我們也不必使用try加 async let, 因為一旦發(fā)生錯誤, 會直接轉(zhuǎn)到你await的那個結(jié)果處, 所以我們不用寫 try await someFunction(), 直接async let xx = someFunction()就完事!

enum NumberError: Error {
    case outOfRange
}

func fibonacci(of number: Int) async throws -> Int {
    if number < 0 || number > 22 {
        throw NumberError.outOfRange
    }

    if number < 2 { return number }
    async let first = fibonacci(of: number - 2)
    async let second = fibonacci(of: number - 1)
    return try await first + second
}

這個例子中我們本來要寫 try await fibonacc(of:), 但是留到了最后寫

  1. 優(yōu)雅的處理外部傳入的閉包
    比如在外部定義了這樣一個方法, 我們不方便直接改造成async await
func fetchLatestNews(completion: @escaping ([String]) -> Void) {
    DispatchQueue.main.async {
        completion(["Swift 5.5 release", "Apple acquires Apollo"])
    }
}

我們可以用一個新的 fetchLatestNews() 方法將這個閉包包起來

func fetchLatestNews() async -> [String] {
    await withCheckedContinuation { continuation in
        fetchLatestNews { items in
            continuation.resume(returning: items)
        }
    }
}

我們可以這樣調(diào)用了

func printNews() async {
    let items = await fetchLatestNews()

    for item in items {
        print(item)
    }
}
  1. 其他
  2. Actors
    這段代碼有問題嗎?
class RiskyCollector {
    var deck: Set<String>

    init(deck: Set<String>) {
        self.deck = deck
    }

    func send(card selected: String, to person: RiskyCollector) -> Bool {
        guard deck.contains(selected) else { return false }

        deck.remove(selected)
        person.transfer(card: selected)
        return true
    }

    func transfer(card: String) {
        deck.insert(card)
    }
}

我們用 SafeCollector actor 來重寫 RiskyCollector

actor SafeCollector {
    var deck: Set<String>

    init(deck: Set<String>) {
        self.deck = deck
    }

    func send(card selected: String, to person: SafeCollector) async -> Bool {
        guard deck.contains(selected) else { return false }

        deck.remove(selected)
        await person.transfer(card: selected)
        return true
    }

    func transfer(card: String) {
        deck.insert(card)
    }
}

值得注意以下幾點:

  1. 用actor關(guān)鍵字來構(gòu)建Actor, 就想class, struct 和 enum

  2. send()方法被async標(biāo)記, 那么它會等待這個方法完成, 即transfer執(zhí)行完

  3. 雖然 transfer(card:) 沒有被async 標(biāo)記, 但是我們?nèi)孕枰谡{(diào)用的時候加上await, 讓他在下一個actor 能發(fā)起這個請求之前一直保持等待狀態(tài)
    actor保證我們能夠隨意的異步使用屬性和方法, 當(dāng)然要保證被async修飾, 所有的 actor-isolated state 都能不會被異步同時訪問
    actor和class的相同點:

  4. 都是引用類型

  5. 都有方法, 屬性, 構(gòu)造方法, subcript

  6. 可以遵循協(xié)議, 可以generic

  7. 所有的方法和屬性都是static, 因為沒有self的概念
    不同點:

  8. 不能繼承, 所以我們構(gòu)造時也更加簡單, 不需要使用convience initializers, overriding, final等關(guān)鍵字

  9. 遵循了特有的Actor協(xié)議, 其他類型都不可以使用

  10. Global actors
    @MainActor global acto保證你只能在主線程訪問他

class OldDataController {
    func save() -> Bool {
        guard Thread.isMainThread else {
            return false
        }

        print("Saving data…")
        return true
    }
}

使用@MainActor可以替代 DispatchQueue.main

class NewDataController {
    @MainActor func save() {
        print("Saving data…")
    }
}
  1. if 作為后綴表達(dá)式(postfix member expressions)

比如

Text("Welcome")
#if os(iOS)
    .font(.largeTitle)
#else
    .font(.headline)
#endif

還可以嵌套

#if os(iOS)
    .font(.largeTitle)
    #if DEBUG
        .foregroundColor(.red)
    #endif
#else
    .font(.headline)
#endif

使用可以很廣泛

let result = [1, 2, 3]
#if os(iOS)
    .count
#else
    .reduce(0, +)
#endif

print(result)
  1. CGFloat 和 Double 可以互相轉(zhuǎn)換
let first: CGFloat = 42
let second: Double = 19
let result = first + second
print(result)

那么result是什么類型?

  1. Codable for enums with associated values
enum Weather: Codable {
    case sun
    case wind(speed: Int)
    case rain(amount: Int, chance: Int)
}

let forecast: [Weather] = [
    .sun,
    .wind(speed: 10),
    .sun,
    .rain(amount: 5, chance: 50)
]

do {
    let result = try JSONEncoder().encode(forecast)
    let jsonString = String(decoding: result, as: UTF8.self)
    print(jsonString)
} catch {
    print("Encoding error: \(error.localizedDescription)")
}
  1. lazy可以使用在局部上下文中
    在方法中
func printGreeting(to: String) -> String {
    print("In printGreeting()")
    return "Hello, \(to)"
}

func lazyTest() {
    print("Before lazy")
    lazy var greeting = printGreeting(to: "Paul")
    print("After lazy")
    print(greeting)
}

lazyTest()

那么打印結(jié)果是?

Before lazy
After lazy
In printGreeting() 
Hello, Paul

因為swift只有在訪問greeting的時候才會執(zhí)行g(shù)reeting這個計算屬性
這個更新有助于幫助我們只在有需要的時候才執(zhí)行代碼, 我們可以先寫好計算的代碼, 但是只有在真正需要的時候才會去執(zhí)行

  1. 屬性包裝器(property wrappers)
    擴(kuò)展了屬性包裝器, 使之可以作為參數(shù)應(yīng)用在方法或者閉包中
    比如我們想要限制下面方法中的入?yún)⒌姆秶?/li>
func setScore1(to score: Int) {
    print("Setting score to \(score)")
}

setScore1(to: 50)
setScore1(to: -50)
setScore1(to: 500)

可以這樣做
先定義一個屬性包裝器

@propertyWrapper
struct Clamped<T: Comparable> {
    let wrappedValue: T

    init(wrappedValue: T, range: ClosedRange<T>) {
        self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

func setScore2(@Clamped(range: 0...100) to score: Int) {
    print("Setting score to \(score)")
}

setScore2(to: 50)
setScore2(to: -50)
setScore2(to: 500)

那么打印結(jié)果分別為 50, 0, 100

  1. 擴(kuò)展靜態(tài)類型推斷
    我們現(xiàn)在有個Theme協(xié)議
protocol Theme { }
struct LightTheme: Theme { }
struct DarkTheme: Theme { }
struct RainbowTheme: Theme { }

定義一個Screen協(xié)議, 有一個theme() 方法

protocol Screen { }

extension Screen {
    func theme<T: Theme>(_ style: T) -> Screen {
        print("Activating new theme!")
        return self
    }
}

創(chuàng)建一個Screen的實例

struct HomeScreen: Screen { }

現(xiàn)在我們可以通過LightTheme()在screen上設(shè)置lightTheme了

let lightScreen = HomeScreen().theme(LightTheme())

為了簡單點, 我們?yōu)門heme擴(kuò)展了一個static屬性 light

extension Theme where Self == LightTheme {
    static var light: LightTheme { .init() }
}

但是因為靜態(tài)屬性的關(guān)系, 在協(xié)議方法theme()中, 每次都必須傳入LightTheme() 對象
但是在swift5.5, 我們就可以這樣寫了

let lightTheme = HomeScreen().theme(.light)

awsome!

參考鏈接: https://www.hackingwithswift.com/articles/233/whats-new-in-swift-5-5

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔫仙,一起剝皮案震驚了整個濱河市哄辣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羡疗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件或渤,死亡現(xiàn)場離奇詭異朋魔,居然都是意外死亡函荣,警方通過查閱死者的電腦和手機(jī)棚菊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門浸踩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人统求,你說我怎么就攤上這事检碗。” “怎么了码邻?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵折剃,是天一觀的道長。 經(jīng)常有香客問我冒滩,道長微驶,這世上最難降的妖魔是什么浪谴? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任开睡,我火速辦了婚禮,結(jié)果婚禮上苟耻,老公的妹妹穿的比我還像新娘篇恒。我一直安慰自己,他們只是感情好凶杖,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布胁艰。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腾么。 梳的紋絲不亂的頭發(fā)上奈梳,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音解虱,去河邊找鬼攘须。 笑死,一個胖子當(dāng)著我的面吹牛殴泰,可吹牛的內(nèi)容都是我干的于宙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼悍汛,長吁一口氣:“原來是場噩夢啊……” “哼捞魁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起离咐,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤谱俭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宵蛀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旺上,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年糖埋,在試婚紗的時候發(fā)現(xiàn)自己被綠了宣吱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞳别,死狀恐怖征候,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祟敛,我是刑警寧澤疤坝,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站馆铁,受9級特大地震影響跑揉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜埠巨,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一历谍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辣垒,春花似錦望侈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侥猬。三九已至,卻和暖如春捐韩,著一層夾襖步出監(jiān)牢的瞬間退唠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工荤胁, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留铜邮,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓寨蹋,卻偏偏與公主長得像松蒜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子已旧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容