Swift5.5 新特性

作者: Just_go | 来源:发表于2021-07-13 13:41 被阅读0次
    1. Async/await
    2. 是个啥
      一言以蔽之, 以前需要用闭包回调来写的代码, 我们现在可以用async/await来写, 这让我们可以抛弃复杂的闭包嵌套代码, 极大的简化了代码, 提升可读性
      举个🌰
      我们先查询历史天气, 再计算出平均温度, 最后上传
    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")
        }
    }
    

    调用的时候

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

    可以发现, 无论是写起来还是阅读都非常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"]
    }
    

    如果想通过这三个属性构造一个User对象, async let 将是最简单的方法 -- 每个方法都是异步的, 等待这三个方法全部执行完, 才会去构建新对象

    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标记, 那么async let将不会等待结果产生, 直到离开当前这个作用域

    在那些会抛出异常的方法中, 我们也不必使用try加 async let, 因为一旦发生错误, 会直接转到你await的那个结果处, 所以我们不用写 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. 优雅的处理外部传入的闭包
      比如在外部定义了这样一个方法, 我们不方便直接改造成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)
            }
        }
    }
    

    我们可以这样调用了

    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关键字来构建Actor, 就想class, struct 和 enum

    2. send()方法被async标记, 那么它会等待这个方法完成, 即transfer执行完

    3. 虽然 transfer(card:) 没有被async 标记, 但是我们仍需要在调用的时候加上await, 让他在下一个actor 能发起这个请求之前一直保持等待状态
      actor保证我们能够随意的异步使用属性和方法, 当然要保证被async修饰, 所有的 actor-isolated state 都能不会被异步同时访问
      actor和class的相同点:

    4. 都是引用类型

    5. 都有方法, 属性, 构造方法, subcript

    6. 可以遵循协议, 可以generic

    7. 所有的方法和属性都是static, 因为没有self的概念
      不同点:

    8. 不能继承, 所以我们构造时也更加简单, 不需要使用convience initializers, overriding, final等关键字

    9. 遵循了特有的Actor协议, 其他类型都不可以使用

    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 作为后缀表达式(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 可以互相转换
    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()
    

    那么打印结果是?

    Before lazy
    After lazy
    In printGreeting() 
    Hello, Paul
    

    因为swift只有在访问greeting的时候才会执行greeting这个计算属性
    这个更新有助于帮助我们只在有需要的时候才执行代码, 我们可以先写好计算的代码, 但是只有在真正需要的时候才会去执行

    1. 属性包装器(property wrappers)
      扩展了属性包装器, 使之可以作为参数应用在方法或者闭包中
      比如我们想要限制下面方法中的入参的范围
    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)
    

    那么打印结果分别为 50, 0, 100

    1. 扩展静态类型推断
      我们现在有个Theme协议
    protocol Theme { }
    struct LightTheme: Theme { }
    struct DarkTheme: Theme { }
    struct RainbowTheme: Theme { }
    

    定义一个Screen协议, 有一个theme() 方法

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

    创建一个Screen的实例

    struct HomeScreen: Screen { }
    

    现在我们可以通过LightTheme()在screen上设置lightTheme了

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

    为了简单点, 我们为Theme扩展了一个static属性 light

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

    但是因为静态属性的关系, 在协议方法theme()中, 每次都必须传入LightTheme() 对象
    但是在swift5.5, 我们就可以这样写了

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

    awsome!

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

    相关文章

      网友评论

        本文标题:Swift5.5 新特性

        本文链接:https://www.haomeiwen.com/subject/vtxspltx.html