Swift 5.5 新特性

Async/await

新舊方式的比較
以前的方式:

func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
    // Complex networking code here; we'll just send back 100,000 random temperatures
    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) {
    // Sum our array then divide by the array size
    DispatchQueue.global().async {
        let total = records.reduce(0, +)
        let average = total / Double(records.count)
        completion(average)
    }
}

func upload(result: Double, completion: @escaping (String) -> Void) {
    // More complex networking code; we'll just send back "OK"
    DispatchQueue.global().async {
        completion("OK")
    }
}

現(xiàn)在的方式:

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

存在的問(wèn)題是

回調(diào)函數(shù)很容易調(diào)用多次阻荒,或者忘記調(diào)用。
函數(shù)參數(shù) @escaping (String) -> Void 看著也不直觀
“回調(diào)地獄”看起來(lái)也不美觀
在Swift 5.0 增加了Result 類型之前屡萤,返回錯(cuò)誤也困難。

Swift 5.5 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"
}

簡(jiǎn)單使用

func processWeather() async {
    let records = await fetchWeatherHistory()
    let average = await calculateAverageTemperature(for: records)
    let response = await upload(result: average)
    print("Server response: \(response)")
}

Async / await 錯(cuò)誤處理
swift 5.5 async 函數(shù)也可以像普通函數(shù)一樣拋出錯(cuò)誤 async throws吊骤。

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!")
    }
}

函數(shù)加上async 并不會(huì)自動(dòng)并行 concurrency

除非特殊處理镀虐,函數(shù)還是順序執(zhí)行耳幢,并不會(huì)自動(dòng)并行斑粱,更不會(huì)自動(dòng)跑在在其他線程。

Xcode 13 playground中運(yùn)行異步代碼

現(xiàn)在(2021-7-25)之前鼓择,暫時(shí)還沒有明顯優(yōu)雅的方式在playground中執(zhí)行async / await 代碼三幻。參考這里,可以這樣執(zhí)行

import Foundation

struct Main {
  static func main() async {
    await doSomething()
  }

  static func doSomething() async {
    print("doSomething")
  }
}

Task.detached {
  await Main.main()
  exit(EXIT_SUCCESS)
}
RunLoop.main.run()

Async / await: sequences

SE-0298提案為swift 引入了AsyncSequence protocol呐能,循環(huán)異步隊(duì)列念搬。 使用AsyncSequence 使用和Sequence幾乎一樣。需要遵循AsyncSequenceAsyncIterator摆出,當(dāng)然next()方法需要時(shí)異步async的朗徊,和Sequence一樣,迭代結(jié)束確認(rèn)返回nil偎漫。

struct DoubleGenerator: AsyncSequence {
    typealias Element = Int

    struct AsyncIterator: AsyncIteratorProtocol {
        var current = 1

        mutating func next() async -> Int? {
            defer { current &*= 2 }

            if current < 0 {
                return nil
            } else {
                return current
            }
        }
    }

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator()
    }
}

運(yùn)行也是常見的 for await 語(yǔ)法爷恳。

func printAllDoubles() async {
    for await number in DoubleGenerator() {
        print(number)
    }
}

更高級(jí)的是,AsyncSequence協(xié)議提供了一些常用的方法象踊,像map()温亲, compactMap(), allSatisfy()

func containsExactNumber() async {
    let doubles = DoubleGenerator()
    let match = await doubles.contains(16_777_216)
    print(match)
}

read-only 屬性

SE-0310提案升級(jí)了swift的只讀屬性,讓其支持了async和throws杯矩。

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

Structured concurrency

SE-0304在async / await 和async sequence的基礎(chǔ)上為swift 引入了一整套的并發(fā)的執(zhí)行栈虚,取消,監(jiān)控的方法史隆。 為了更好說(shuō)明魂务,我們先假設(shè)這兩個(gè)情況

enum LocationError: Error {
    case unknown
}

func getWeatherReadings(for location: String) async throws -> [Double] {
    switch location {
    case "London":
        return (1...100).map { _ in Double.random(in: 6...26) }
    case "Rome":
        return (1...100).map { _ in Double.random(in: 10...32) }
    case "San Francisco":
        return (1...100).map { _ in Double.random(in: 12...20) }
    default:
        throw LocationError.unknown
    }
}

func fibonacci(of number: Int) -> Int {
    var first = 0
    var second = 1

    for _ in 0..<number {
        let previous = first
        first = second
        second = previous + first
    }

    return first
}

在swift的項(xiàng)目中,可以這么執(zhí)行(如果在playground文件中逆害,可以使用上文的方法)

@main
struct Main {
    static func main() async throws {
        let readings = try await getWeatherReadings(for: "London")
        print("Readings are: \(readings)")
    }
}

結(jié)構(gòu)化并發(fā)头镊,實(shí)際上是引入了兩個(gè)類型蚣驼,TaskTaskGroup來(lái)魄幕,獨(dú)立或者協(xié)同地運(yùn)行并發(fā)代碼。 簡(jiǎn)單來(lái)說(shuō)颖杏,你只要將異步代碼傳入Task對(duì)象纯陨,就會(huì)立即在background 線程上運(yùn)行,然后你用await等待結(jié)果就好留储。

func printFibonacciSequence() async {
    let task1 = Task { () -> [Int] in
        var numbers = [Int]()

        for i in 0..<50 {
            let result = fibonacci(of: i)
            numbers.append(result)
        }

        return numbers
    }

    let result1 = await task1.value
    print("The first 50 numbers in the Fibonacci sequence are: \(result1)")
}

需要注意翼抠,代碼中明確指定了Task { () -> [Int] in 這樣,Swift就會(huì)知道task需要返回获讳,而如果你的異步代碼比較簡(jiǎn)單阴颖,可以像下面這樣寫:

let task1 = Task {
    (0..<50).map(fibonacci)
}

再次,task會(huì)在創(chuàng)建之后立即運(yùn)行丐膝,但是在斐波那契函數(shù)得到結(jié)果之后量愧,钾菊,printFibonacciSequence()函數(shù)會(huì)繼續(xù)運(yùn)行在原來(lái)的線程。

task參數(shù)屬于non-escaping 閉包

主要到task的函數(shù)參數(shù)中并沒有標(biāo)注@escape偎肃,因?yàn)閠ask會(huì)立即執(zhí)行傳入的函數(shù)煞烫,而不是存儲(chǔ)然后之后執(zhí)行。因此累颂,如果你在class或者struct中使用Task滞详,你不需要self來(lái)獲取屬性和方法。

上述函數(shù)中紊馏,通過(guò)await task.value來(lái)獲取task的值料饥,如果你不關(guān)心task 返回的值,你也不需要存儲(chǔ)task朱监。 對(duì)于會(huì)拋出錯(cuò)誤的異步任務(wù)稀火,從task的value取值,也會(huì)觸發(fā)錯(cuò)誤赌朋,因此仍然需要try await凰狞。

func runMultipleCalculations() async throws {
    let task1 = Task {
        (0..<50).map(fibonacci)
    }

    let task2 = Task {
        try await getWeatherReadings(for: "Rome")
    }

    let result1 = await task1.value
    let result2 = try await task2.value
    print("The first 50 numbers in the Fibonacci sequence are: \(result1)")
    print("Rome weather readings are: \(result2)")
}

Swift為task內(nèi)置了幾種優(yōu)先級(jí)highdefault沛慢,lowbackground赡若。如果未指定,會(huì)默認(rèn)設(shè)置為default团甲,當(dāng)然你可以顯式指定Task(priority: .high)逾冬。當(dāng)然如果你在Apple的平臺(tái)上,你會(huì)使用很熟悉的優(yōu)先級(jí)躺苦,userInitiated對(duì)應(yīng)high身腻,utility對(duì)應(yīng)low,當(dāng)然你不能使用userInteractive匹厘,它是為主線程保留的嘀趟。 當(dāng)然,Task也為我們提供了幾個(gè)靜態(tài)的方法愈诚。

  • Task.sleep()會(huì)暫定當(dāng)前任務(wù)一定時(shí)間(納秒她按,也就是1_000_000_000為1秒)
  • Task.checkCancellation()會(huì)檢查當(dāng)前任務(wù)是否被cancel()取消,如果已經(jīng)取消了,會(huì)拋出CancellationError錯(cuò)誤。
  • Task.yield()會(huì)暫停當(dāng)前任務(wù)一定時(shí)間蔬将,讓給其他等待的任務(wù)讓出點(diǎn)時(shí)間,這點(diǎn)在循環(huán)做一些繁重的任務(wù)會(huì)很有用陵刹。
func cancelSleepingTask() async {
    let task = Task { () -> String in
        print("Starting")
        await Task.sleep(1_000_000_000)
        try Task.checkCancellation()
        return "Done"
    }

    // The task has started, but we'll cancel it while it sleeps
    task.cancel()

    do {
        let result = try await task.value
        print("Result: \(result)")
    } catch {
        print("Task was cancelled.")
    }
}

上述代碼中,``Task.checkCancellation()會(huì)檢測(cè)到task已經(jīng)被取消了欢嘿,會(huì)立即跑出CancellationError的錯(cuò)誤衰琐,當(dāng)然只有在嘗試task.value取值的時(shí)候巡验,才會(huì)拋出。

使用task.result來(lái)獲取到Result類型的值

你可以使用task.result來(lái)獲取到Result類型的值碘耳,上述代碼會(huì)返回Result<String, Error>显设。當(dāng)然你也就不需要try來(lái)捕捉錯(cuò)誤了。

對(duì)于復(fù)雜的任務(wù)辛辨,可以使用 task group來(lái)組織更多的task捕捂。

func printMessage() async {
    let string = await withTaskGroup(of: String.self) { group -> String in
        group.async { "Hello" }
        group.async { "From" }
        group.async { "A" }
        group.async { "Task" }
        group.async { "Group" }

        var collected = [String]()

        for await value in group {
            collected.append(value)
        }

        return collected.joined(separator: " ")
    }

    print(string)
}

不要將withTaskGroup里面的代碼復(fù)制到外面,編輯不會(huì)報(bào)錯(cuò)斗搞,但是自然會(huì)有潛在的問(wèn)題指攒。 所有task group中的任務(wù)應(yīng)該返回同樣的數(shù)據(jù)類型。復(fù)雜情況僻焚,你可能需要一個(gè)有associated值的enum來(lái)準(zhǔn)確取值允悦,當(dāng)然你還可以使用async let 綁定的替代方案。 task group的值需要所有的task都完成虑啤,但是每個(gè)task執(zhí)行完順序是不保證的隙弛。 你可以在task group中處理錯(cuò)誤,或者你可以使用withThrowingTaskGroup()把錯(cuò)誤拋出狞山,這樣也就需要try的方式來(lái)取值全闷。

func printAllWeatherReadings() async {
    do {
        print("Calculating average weather…")

        let result = try await withThrowingTaskGroup(of: [Double].self) { group -> String in
            group.async {
                try await getWeatherReadings(for: "London")
            }

            group.async {
                try await getWeatherReadings(for: "Rome")
            }

            group.async {
                try await getWeatherReadings(for: "San Francisco")
            }

            // Convert our array of arrays into a single array of doubles
            let allValues = try await group.reduce([], +)

            // Calculate the mean average of all our doubles
            let average = allValues.reduce(0, +) / Double(allValues.count)
            return "Overall average temperature is \(average)"
        }

        print("Done! \(result)")
    } catch {
        print("Error calculating data.")
    }
}

上述每個(gè) async任務(wù),你可以簡(jiǎn)化使用for location in ["London", "Rome", "San Francisco"] {來(lái)調(diào)用萍启。 task group 提供一個(gè)cancelAll()的方法來(lái)取消所有的task总珠。之后你仍然可以給group添加異步任務(wù)。當(dāng)然勘纯,你可以使用asyncUnlessCancelled()來(lái)跳過(guò)添加任務(wù)局服,如果group已經(jīng)被取消—— 檢查Boolean的返回值類判斷group是否被取消。

async let 綁定

SE-0317引入了一種更簡(jiǎn)易的語(yǔ)法async let來(lái)創(chuàng)建和等待子任務(wù)驳遵。這個(gè)可以作為task group的替代方法淫奔,特別是你需要使用不同數(shù)據(jù)類型的異步任務(wù)時(shí)候。

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

可以使用如下簡(jiǎn)單并發(fā)取值超埋。

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的上下文中搏讶,使用async let

你只能在async的上下文中霍殴,使用async let。而且如果你不去使用await取值系吩,swift會(huì)在其作用于隱式等待来庭。

綁定拋錯(cuò)的異步方法的時(shí)候,你也不需要使用try關(guān)鍵詞穿挨。只需要取值時(shí)候try await月弛。 更高級(jí)的是肴盏,我們可以遞歸的使用async let語(yǔ)法。

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
}

Continuation函數(shù)(轉(zhuǎn)換回調(diào)異步為async函數(shù))

SE-0300提供了把老的回調(diào)式異步函數(shù)轉(zhuǎn)換為async函數(shù)的方法帽衙。 例如菜皂,有如下的回調(diào)函數(shù)

func fetchLatestNews(completion: @escaping ([String]) -> Void) {
    DispatchQueue.main.async {
        completion(["Swift 5.5 release", "Apple acquires Apollo"])
    }
}

swift 5.5之后,你不需要重寫你的所有代碼厉萝,你只需要使用withCheckedContinuation()函數(shù)包裹就好恍飘。

func fetchLatestNews() async -> [String] {
    await withCheckedContinuation { continuation in
        fetchLatestNews { items in
            continuation.resume(returning: items)
        }
    }
}
  • resume(returning:)函數(shù)返回,你異步要返回的數(shù)據(jù)谴垫。
  • 確保resume(returning:)函數(shù)只調(diào)用一次章母。在withCheckedContinuation()函數(shù)中,swift會(huì)告警甚至?xí)罎⒋a翩剪,當(dāng)然這會(huì)有性能損耗乳怎。
  • 如果有更高性能要求,或者你確保你的代碼不會(huì)有問(wèn)題前弯,你可以使用``withUnsafeContinuation()蚪缀。

Actors

SE-0306引入了actor,概念上和class很相似恕出,但是swfit確保了椿胯,actor中的變量在任意時(shí)間段內(nèi)只會(huì)被一個(gè)線程獲取,這也就確保了actor在并發(fā)環(huán)境下的安全剃根。 例如下面的代碼

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

在單線程的環(huán)境中都是OK的哩盲。但是在多線程的環(huán)境中,我們代碼就有了潛在的資源競(jìng)爭(zhēng)風(fēng)險(xiǎn)狈醉,這也就導(dǎo)致了廉油,當(dāng)代碼并行運(yùn)行時(shí),代碼的執(zhí)行結(jié)果會(huì)可能不同苗傅。 假設(shè)我們調(diào)用send(card:to:) 在同一時(shí)間調(diào)用多次抒线,

  1. 第一個(gè)線程檢查card是否在deck,存在渣慕,繼續(xù)
  2. 第二個(gè)線程也檢查card是否在deck嘶炭,存在,也繼續(xù)
  3. 第一個(gè)線程刪除了deck中的card然后轉(zhuǎn)移給了第二個(gè)人逊桦。
  4. 第二個(gè)線程嘗試刪除deck中的card眨猎,但是實(shí)際上已經(jīng)不存在了,但是它還是把card轉(zhuǎn)移給了另一個(gè)人强经。

這樣就導(dǎo)致給一個(gè)人轉(zhuǎn)移了兩個(gè)卡片睡陪。絕對(duì)的麻煩。 Actor通過(guò)actor isolation隔離的方式解決這個(gè)問(wèn)題:

  • 只能從外部異步地讀取到actor的屬性和方法,
  • 不能從外部寫存儲(chǔ)后的屬性

swift 內(nèi)部通過(guò)隊(duì)列的方式避免資源競(jìng)爭(zhēng)兰迫,因此應(yīng)能不會(huì)很好信殊。 對(duì)于上述的例子,我們可以改寫為:

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. 通過(guò)actor關(guān)鍵詞來(lái)創(chuàng)建一個(gè)Actor汁果,這個(gè)是swift 新加的類型涡拘。
  2. send()方法被標(biāo)為async,因?yàn)樗枰欢〞r(shí)間來(lái)完成card轉(zhuǎn)移据德。
  3. transfer(card:) 并沒有標(biāo)準(zhǔn)為async鳄乏,但是我們?nèi)匀恍枰?code>await 來(lái)調(diào)用晋控,因?yàn)樾枰却?code>SafeCollector actor能夠處理請(qǐng)求汞窗。

更細(xì)節(jié)來(lái)說(shuō),actor內(nèi)部可以任意讀寫屬性和方法赡译,但是和另一個(gè)actor交互的時(shí)候就必須使用異步的方式仲吏。這樣就保證了線程安全,而且更棒的事編譯后保證了這一點(diǎn)蝌焚。 actor和class很像

  • 都是引用類型裹唆,因此它們可以被用來(lái)分享狀態(tài)。
  • 都有方法只洒,屬性许帐,構(gòu)造器,和下標(biāo)方法毕谴。
  • 可以遵循協(xié)議和泛型
  • 兩者靜態(tài)屬性和方法都是一樣的成畦,因?yàn)樗鼈儧]有self,因此也就不需要隔離涝开。

當(dāng)然actor相比class有兩個(gè)最大的不同:

  1. Actor暫時(shí)不支持繼承循帐,因此它們的構(gòu)造器initializer就簡(jiǎn)單多了——不需要convenience initializer,override舀武,和final 關(guān)鍵詞等拄养。這個(gè)后續(xù)可能會(huì)改變。
  2. 所有的actor隱式的遵循了Actor的協(xié)議银舱,其他的類型不能使用這個(gè)協(xié)議瘪匿。

最好的actor描述:“actors pass messages, not memory.”

actor不是直接訪問(wèn)別的acotr內(nèi)存,或者調(diào)用它們的方法寻馏,而是發(fā)送消息棋弥,讓swift runtime來(lái)安全處理數(shù)據(jù)。

Global actors

SE-0316引入了全局actor來(lái)隔離全局狀態(tài)避免數(shù)據(jù)競(jìng)爭(zhēng)操软。 目前來(lái)說(shuō)是引入了一個(gè)@MainActor來(lái)標(biāo)柱裝飾你的屬性和方法嘁锯,讓其保證只在主線程運(yùn)行宪祥。 對(duì)于app來(lái)說(shuō)聂薪,UI更新就需要保證在主線程家乘,以前的方式是使用DispatchQueue.main。swift 5.5之后藏澳,就簡(jiǎn)單了

class NewDataController {
    @MainActor func save() {
        print("Saving data…")
    }
}

@MainActor標(biāo)柱之后仁锯,必須異步調(diào)用。

就像上文翔悠,這里實(shí)際上是actor业崖,因此,我們需要使用await蓄愁,async let 等來(lái)調(diào)用save() @MainActor底層是一個(gè)全局的actor底層是MainActor的結(jié)構(gòu)體双炕。其中有一個(gè)靜態(tài)的run()方法來(lái)讓我們代碼在主線程中執(zhí)行,而且也能夠返回執(zhí)行結(jié)果撮抓。

Sendable協(xié)議和@Sendable閉包

SE-0302支持了“可傳送”的數(shù)據(jù)妇斤,也就是可以安全的向另一個(gè)線程傳送數(shù)據(jù)。也就是引入了新的Sendable protocol和裝飾函數(shù)的@Sendable屬性丹拯。 默認(rèn)線程安全的有

  • 所有Swift核心的值類型站超, BoolInt乖酬,String
  • 包裹的值類型的可選值(Optional)死相。
  • swift標(biāo)準(zhǔn)庫(kù)值類型的集合,例如咬像,Array<String>算撮, Dictionary<Int, String>
  • 值類型的元組(Tuple)
  • 元類型(Metatype),例如String.self

上述的值县昂,都遵循了Sendable的協(xié)議肮柜。 而對(duì)于自定義的類型,如果滿足下面的情況

  • Actor自動(dòng)遵循Sendable七芭,因?yàn)閍ctor內(nèi)部異步的處理數(shù)據(jù)素挽。
  • 自定義的structenum,如果它們只包含遵循Sendable的值也會(huì)自動(dòng)遵循Sendable狸驳,這點(diǎn)和Codable很像预明。
  • class能夠遵循Sendable,如果1. 繼承自NSObject耙箍,或者2. 不繼承撰糠,而且所有的屬性是常量的且能夠遵循Sendable,還有類得標(biāo)注為final來(lái)防止將來(lái)的繼承辩昆。

Swift讓函數(shù)和閉包阅酪,標(biāo)注@Sendable,來(lái)能夠并發(fā)調(diào)用。例如术辐,Task的初始化函數(shù)標(biāo)注了@Sendable砚尽,下面代碼能夠并發(fā)執(zhí)行,因?yàn)槠鋬?nèi)捕獲的是常量辉词。

func printScore() async { 
    let score = 1

    Task { print(score) }
    Task { print(score) }
}

如果score是變量必孤,就不能在task內(nèi)用了。

因?yàn)?code>Task會(huì)并發(fā)執(zhí)行瑞躺,如果是變量敷搪,就存在數(shù)據(jù)競(jìng)爭(zhēng)了。

你可以在自己代碼標(biāo)注@Sendable幢哨,這樣也會(huì)強(qiáng)制上述的規(guī)則(值捕獲)赡勘。

func runLater(_ function: @escaping @Sendable () -> Void) -> Void {
    DispatchQueue.global().asyncAfter(deadline: .now() + 3, execute: function)
}

鏈?zhǔn)秸{(diào)用中支持#if語(yǔ)法

SE-0308中Swift支持了在鏈?zhǔn)秸{(diào)用( postfix member expression)使用#if的條件判斷表達(dá)式。這個(gè)乍看有點(diǎn)費(fèi)解捞镰,其實(shí)是為了解決SwiftUI中根據(jù)條件添加view的修飾器闸与。

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

支持嵌套調(diào)用

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

當(dāng)然,也不是非得在Swift UI中使用

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

print(result)

#if只能用在.操作

只有.操作才算是postfix member expression曼振,所以#if不能用在 +几迄,[]等操作上。

CGFloat 和 Double 隱式轉(zhuǎn)換

SE-0307改進(jìn)為開發(fā)帶來(lái)了巨大的便利:Swift 能夠在大多數(shù)情況下隱式轉(zhuǎn)換CGFloatDouble冰评。

let first: CGFloat = 42
let second: Double = 19
let result = first + second
print(result)

Swift會(huì)有限使用Double映胁,更棒的是,不需要重寫原來(lái)的代碼甲雅,列入解孙,Swift UI中的scaleEffect()仍然可以使用CGFloat,swift 內(nèi)部轉(zhuǎn)換為Double抛人。

Codable支持enum 關(guān)聯(lián)值

SE-0295升級(jí)了Swift Codable弛姜,讓其能夠支持枚舉enum關(guān)聯(lián)值。之前只有遵循rawRepresentable的enum才能使用Codable妖枚。

enum Weather: Codable {
    case sun
    case wind(speed: Int)
    case rain(amount: Int, chance: Int)
}

現(xiàn)在能使用JSONEncoder的

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

// [{"sun":{}},{"wind":{"speed":10}},{"sun":{}},{"rain":{"amount":5,"chance":50}}]

上面json key 可以使用CodingKey來(lái)自定義廷臼。

函數(shù)中支持lazy關(guān)鍵詞

swift中lazy關(guān)鍵詞能夠讓屬性延遲求值,現(xiàn)在swift 5.5之后绝页,函數(shù)中也能使用lazy關(guān)鍵詞了荠商。

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()

property wrapper 可以裝飾到 function 和 closure 參數(shù)

SE-0293擴(kuò)展了property wrapper讓其能夠裝飾到函數(shù)和閉包參數(shù)。 例如原來(lái)的函數(shù)

func setScore1(to score: Int) {
    print("Setting score to \(score)")
}
setScore1(to: 50)
setScore1(to: -50)
setScore1(to: 500)
// Setting score to 50
// Setting score to -50
// Setting score to 500

使用property wrapper可以续誉,用來(lái)固定score的參數(shù)莱没。

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

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

現(xiàn)在使用的

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

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

// Setting score to 50
// Setting score to 0
// Setting score to 100

擴(kuò)展泛型函數(shù)中協(xié)議靜態(tài)成員的查找

SE-0299 提升了Swift對(duì)泛型函數(shù)中協(xié)議靜態(tài)成員查找的能力。這點(diǎn)實(shí)際上也很能提升Swift UI的書寫的便利酷鸦。 以前需要這么寫

Toggle("Example", isOn: .constant(true))
    .toggleStyle(SwitchToggleStyle())

現(xiàn)在這么寫饰躲,就很方便

Toggle("Example", isOn: .constant(true))
    .toggleStyle(.switch)

跟具體來(lái)說(shuō)牙咏,假設(shè)我們有如下的協(xié)議和結(jié)構(gòu)體

protocol Theme { }
struct LightTheme: Theme { }
struct DarkTheme: Theme { }
struct RainbowTheme: Theme { }

我們?cè)俣x一個(gè)Screen協(xié)議,有一個(gè)theme的泛型函數(shù)嘹裂,來(lái)設(shè)置主題妄壶。

protocol Screen { }

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

現(xiàn)在我們有個(gè) Screen的結(jié)構(gòu)體

struct HomeScreen: Screen { }

我們可以指定screen的主題為

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

現(xiàn)在Swift 5.5之后,我們可以在Theme協(xié)議上加個(gè)靜態(tài)的屬性

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

現(xiàn)在Swift 5.5 設(shè)置主題就簡(jiǎn)單了

let lightTheme = HomeScreen().theme(.light)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焦蘑,一起剝皮案震驚了整個(gè)濱河市盯拱,隨后出現(xiàn)的幾起案子盒发,更是在濱河造成了極大的恐慌例嘱,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宁舰,死亡現(xiàn)場(chǎng)離奇詭異拼卵,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蛮艰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門腋腮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人壤蚜,你說(shuō)我怎么就攤上這事即寡。” “怎么了袜刷?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵聪富,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我著蟹,道長(zhǎng)墩蔓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任萧豆,我火速辦了婚禮奸披,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涮雷。我一直安慰自己阵面,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布洪鸭。 她就那樣靜靜地躺著样刷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卿嘲。 梳的紋絲不亂的頭發(fā)上颂斜,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音拾枣,去河邊找鬼沃疮。 笑死盒让,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的司蔬。 我是一名探鬼主播邑茄,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼俊啼!你這毒婦竟也來(lái)了肺缕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤授帕,失蹤者是張志新(化名)和其女友劉穎同木,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跛十,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡彤路,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芥映。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洲尊。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖奈偏,靈堂內(nèi)的尸體忽然破棺而出坞嘀,到底是詐尸還是另有隱情,我是刑警寧澤惊来,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布丽涩,位于F島的核電站,受9級(jí)特大地震影響唁盏,放射性物質(zhì)發(fā)生泄漏内狸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一厘擂、第九天 我趴在偏房一處隱蔽的房頂上張望昆淡。 院中可真熱鬧,春花似錦刽严、人聲如沸昂灵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)眨补。三九已至,卻和暖如春倒脓,著一層夾襖步出監(jiān)牢的瞬間撑螺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工崎弃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甘晤,地道東北人含潘。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像线婚,于是被迫代替她去往敵國(guó)和親遏弱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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

  • Async/await 是個(gè)啥一言以蔽之, 以前需要用閉包回調(diào)來(lái)寫的代碼, 我們現(xiàn)在可以用async/await來(lái)...
    Just_go閱讀 1,080評(píng)論 0 1
  • 本篇主要是對(duì)《A站 的 Swift 實(shí)踐》[https://ming1016.github.io/2021/05/...
    星光社的戴銘閱讀 729評(píng)論 0 1
  • 前言 非常感謝大家抽出生命中寶貴的一段時(shí)間來(lái)聽我接下來(lái)的一大段關(guān)于寫文章那些事的嘮叨塞弊。寫文章的好處看看《覺醒年代》...
    星光社的戴銘閱讀 1,169評(píng)論 2 7
  • Swift 5.5 內(nèi)置于 Xcode 13漱逸,雖然版本號(hào)只增加了 0.1,看似是一個(gè)小版本升級(jí)游沿,但卻帶來(lái)了非常多的...
    YungFan閱讀 1,304評(píng)論 3 0
  • 簡(jiǎn)介:APP 啟動(dòng)速度的重要性不言而喻奏候。高德地圖是一個(gè)有著上億用戶的超級(jí) APP循集,本文從喚端技術(shù)、H5 啟動(dòng)頁(yè)蔗草、下...
    Kepler_II閱讀 548評(píng)論 0 0