Swift5.5 新特性

  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

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容