Swift5.5DocCNotifications,苹果WWDC21带来的最大技术变化

Posted 阿里巴巴淘系技术团队官网博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift5.5DocCNotifications,苹果WWDC21带来的最大技术变化相关的知识,希望对你有一定的参考价值。

WWDC (苹果开发者大会) 2021已经落下帷幕,今年的WWDC 提供了 200 多个深度课程,以帮助开发者了解WWDC2021 引入的新技术,本文会帮国内开发者梳理部分WWDC 2021带来的技术上的变化。

Swift5.5

WWDC2021 给我们带来了Swift 5.5,这是Swift 语言最新的版本,在这个版本中有许多重大的更新,下面会大家详细介绍一下Swift 5.5的一些重要更新。

  Swift Concurrency


Swift 5.5 中最大的更新就是引入了全新的并发编程方式,包括async/await语法、结构化并发、Actor等,新的并发编程方式解决了我们以往使用回调的种种缺陷 (嵌套地狱、回调错误处理麻烦、回调分支编写困难等),为开发者带来了极大的便利。

  • async/await

过去我们编写异步代码都是通过回调的方式,如下:

func processImageData2a(completionBlock: (_ result: Image?, _ error: Error?) -> Void) {
    loadWebResource("dataprofile.txt") { dataResource, error in
        guard let dataResource = dataResource else {
            completionBlock(nil, error)
            return
        }
        loadWebResource("imagedata.dat") { imageResource, error in
            guard let imageResource = imageResource else {
                completionBlock(nil, error)
                return
            }
            decodeImage(dataResource, imageResource) { imageTmp, error in
                guard let imageTmp = imageTmp else {
                    completionBlock(nil, error)
                    return
                }
                dewarpAndCleanupImage(imageTmp) { imageResult, error in
                    guard let imageResult = imageResult else {
                        completionBlock(nil, error)
                        return
                    }
                    completionBlock(imageResult)
                }
            }
        }
    }
}


processImageData2a { image, error in
    guard let image = image else {
        display("No image today", error)
        return
    }
    display(image)
}

通过回调的方式编写异步代码有以下缺点:

  • 阅读不直观

  • 嵌套逻辑复杂

  • 错误处理麻烦

  • 分支逻辑难以处理

  • 经常会忘了回调或者返回

在Swift 5.5中为了解决上述回调方式的缺点,引入了async/await语法,可以帮助我们快速的编写异步代码,通过async/await上述代码可以变成如下同步代码:

func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image


func processImageData() async throws -> Image {
  let dataResource  = try await loadWebResource("dataprofile.txt")
  let imageResource = try await loadWebResource("imagedata.dat")
  let imageTmp      = try await decodeImage(dataResource, imageResource)
  let imageResult   = try await dewarpAndCleanupImage(imageTmp)
  return imageResult
}

正如上述代码所展现的,所有的闭包和缩进都消失了,你可以按顺序依次书写代码——除了 await 关键字,它看起来就和同步代码一样。

关于async函数的工作方式,有一些规则需要注意:

  • 同步函数不能简单地直接调用异步函数, Swift 编译器会抛出错误。

  • 异步函数可以调用其他异步函数,但如果需要,它们也可以调用常规的同步函数。

  • 如果有可以以相同方式调用的异步和同步函数,Swift 将优先选择与当前上下文匹配的任何一个, 如果当前执行上下文是异步的,那么 Swift 将调用异步函数,否则它将调用同步函数。

最后一点很重要,因为它允许库的作者提供他们代码的同步和异步版本,而无需专门命名异步函数。

  • Structured concurrency

在介绍结构化并发之前,我们先来看一个案例:

func chopVegetables() async throws -> [Vegetable] { ... }
func marinateMeat() async -> Meat { ... }
func preheatOven(temperature: Double) async throws -> Oven { ... }


// ...


func makeDinner() async throws -> Meal {
  let veggies = try await chopVegetables()  // 处理蔬菜
  let meat = await marinateMeat()           // 腌制肉
  let oven = try await preheatOven(temperature: 350) //预热烤箱


  let dish = Dish(ingredients: [veggies, meat])   // 把蔬菜和肉装盘
  return try await oven.cook(dish, duration: .hours(3))  // 用烤箱做出晚餐
}

上面处理蔬菜、腌制肉、预热烤箱等都是异步执行的,但是上述三个步骤仍然是串行执行的,这使得做晚餐的时间变长了,为了让晚餐准备时间变短,我们需要让处理蔬菜、腌制肉、预热烤箱几个步骤并发执行

为了解决上述问题,Swift 5.5中引入了Structured concurrency(结构化并发),下面是维基百科中的解释:

结构化并发是一种编程范式,旨在通过使用结构化的并发编程方法来提高计算机程序的清晰度、质量和研发效能。

核心理念是通过具有明确入口和出口点并确保所有生成的子任务在退出前完成的控制流构造来封装并发执行任务(这里包括内核和用户线程和进程)。这种封装允许并发任务中的错误传播到控制结构的父作用域,并由每种特定计算机语言的本机错误处理机制进行管理。尽管存在并发性,但它允许控制流通过源代码的结构保持显而易见。为了有效,这个模型必须在程序的所有级别一致地应用——否则并发任务可能会泄漏、成为孤立的或无法正确传播运行时错误。(来自维基百科)

使用结构化并发,上述制作晚餐的过程可以通过下面的方式进行:

func makeDinner() async throws -> Meal {
  // Prepare some variables to receive results from our concurrent child tasks
  var veggies: [Vegetable]?
  var meat: Meat?
  var oven: Oven?


  enum CookingStep { 
    case veggies([Vegetable])
    case meat(Meat)
    case oven(Oven)
  }


  // Create a task group to scope the lifetime of our three child tasks
  try await withThrowingTaskGroup(of: CookingStep.self) { group in
    group.async {
      try await .veggies(chopVegetables())
    }
    group.async {
      await .meat(marinateMeat())
    }
    group.async {
      try await .oven(preheatOven(temperature: 350))
    }


    for try await finishedStep in group {
      switch finishedStep {
        case .veggies(let v): veggies = v
        case .meat(let m): meat = m
        case .oven(let o): oven = o
      }
    }
  }


  // If execution resumes normally after `withTaskGroup`, then we can assume
  // that all child tasks added to the group completed successfully. That means
  // we can confidently force-unwrap the variables containing the child task
  // results here.
  let dish = Dish(ingredients: [veggies!, meat!])
  return try await oven!.cook(dish, duration: .hours(3))
}

上述代码中chopVegetables、marinateMeat 和preheatOven 将并发运行,并且可能以任何顺序进行。

无论哪种情况,任务组都会自然地将状态从子任务传播到父任务;在这个例子中,如果菜刀发生了事故,chopVegetables() 函数可能会抛出一个错误。

抛出的错误完成了切菜的子任务。正如预期的那样,该错误随后将传播到 makeDinner() 函数之外。在出现此错误退出 makeDinner() 函数的主体时,任何尚未完成的子任务(腌肉或预热烤箱,可能两者)将自动取消。

结构化并发意味着我们不必手动传播错误和管理取消;如果在调用 withTaskGroup 后继续正常执行,我们可以假设它的所有子任务都成功完成。

  • Actors

Swift 5.5引入了Actor,它在概念上类似于在并发环境中可以安全使用的类。Swift 确保在任何给定时间只能由单个线程访问 Actor 内的可变状态,这有助于在编译器级别消除各种严重的错误。

我们可以先一起看一个Swift中的Class,如下:

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

以上是关于Swift5.5DocCNotifications,苹果WWDC21带来的最大技术变化的主要内容,如果未能解决你的问题,请参考以下文章