美文网首页Swift
Swift 5.5 | Async throws/ Try aw

Swift 5.5 | Async throws/ Try aw

作者: 清無 | 来源:发表于2022-03-25 17:31 被阅读0次

    简介

    为了解决传统异步编程中回调block无限“套娃”的问题,苹果官方于Swift5.5版本引入了新的异步编程理念try await,类似于同步的异步(异步等待)方式,大大简化了异步编程的代码,提高了逻辑清晰性。

    async throws可用于下列标识:

    • 属性(计算属性)
    • 方法
    • 初始化器

    示例

    异步下载一张图片

    enum ImageDownloadError: Error {
        case failed
    }
    
    func downloadImage(url: String) async throws -> UIImage {
       try Task.checkCancellation()
    
        guard let aURL = URL(string: url) else {
            throw URLError(.badURL)
        }
        let request = URLRequest(url: aURL)
        print("Started downloading... \(url)")
        let (data, _) = try await URLSession.shared.data(for: request)
        guard data.count > 0, let image = UIImage(data: data) else {
            throw ImageDownloadError.failed
        }
        print("Finished downloading")
        return image
    }
    
    
    let task = Task {
        do {
            let idx = 1 + arc4random() % 20
            let image = try await downloadImage(url: "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/HighResolution/\(idx).jpg")
            print("Success: ", image)
        } catch {
            print("Failure: \(error.localizedDescription)")
        }
    }
    
    // 0.1s后取消任务,这时候downloadImage异步任务直接会被thrown,
    // 不会继续执行session网络请求
    DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
        task.cancel()
    }
    
    • 如果不Task.checkCancellation的话,一旦URLSession任务发出去,就会被执行完(即使外部task已经被cancel)
    • 如果进行了Task.checkCancellation,那么当URLSession任务完成的时候,如果检测到所在task已经被cancel了,则不会返回URLSession的执行结果,而是直接throw cancelled error
    • 此外,如果不想在Task被cancel的时候抛出异常,而是当成正常操作,也可以如下操作:
    if Task.isCancelled {
    // 自定义默认返回值
      return someDefaultValue
    }
    

    或者,抛出自定义error:

    if Task.isCancelled {
      throw MyError.some
    }
    

    打印:
    Started downloading... image download url
    Failure: cancelled

    • 异步串行任务
    do {
            var getOneImageUrl: String {
                let idx = 1 + arc4random() % 20
                return "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/HighResolution/\(idx).jpg"
            }
            let image1 = try await downloadImage(url: getOneImageUrl)
            print("Success image1: ", image1)
            
            let image2 = try await downloadImage(url: getOneImageUrl)
            print("Success image2: ", image2)
            
            let image3 = try await downloadImage(url: getOneImageUrl)
            print("Success image3: ", image3)
            
        } catch {
            print("Failure: \(error.localizedDescription)")
        }
    

    打印:
    Started: 3.jpg
    Finished: 3.jpg
    Success image1: <UIImage:0x600003769200 anonymous {3688, 2459} renderingMode=automatic>
    Started: 11.jpg
    Finished: 11.jpg
    Success image2: <UIImage:0x600003760a20 anonymous {3886, 2595} renderingMode=automatic>
    Started: 1.jpg
    Finished: 1.jpg
    Success image3: <UIImage:0x600003764d80 anonymous {6000, 4000} renderingMode=automatic>

    • 主线程方法
    @MainActor
    func showImage(_ image: UIImage) {
        imageView.image = image
    }
    
    • 也可以使用MainActorrun方法来包裹需要主线程执行的block,类似于GCD main queue:
    /*
    public static func run<T>(
      resultType: T.Type = T.self, 
      body: @MainActor @Sendable () throws -> T
    ) async rethrows -> T
    */
    
    var img: UIImage?
    ...
    // give the `img` to some specific value
    ...
    // need to capture the mutable value into capture list or compiles error
    MainActor.run{ [img] in
      self.imageView.image = img
    }
    
    ...
    // or can also be replaced by this:
    let img: UIImage
    if xx {
      img = someValue
    } else {
      img = nil
    }
    // and then, the `img` property will be immutable anymore, 
    // so it is can be used in `MainActor` context
    MainActor.run{
      self.imageView.image = img
    }
    

    @MainActor标识表示在主线程调用该方法(自动切换到)

    • 调用
    let image1 = try await downloadImage(url: getOneImageUrl)
    print("Success image1: ", image1)
    await showImage(image1)
    

    这里有两个新概念:Task和MainActor,使用Task的原因是在同步线程和异步线程之间,我们需要一个桥接,我们需要告诉系统开辟一个异步环境,否则编译器会报 'async' call in a function that does not support concurrency的错误。 另外Task表示开启一个任务。@MainActor表示让showImage方法在主线程执行。

    使用 async-await并不会阻塞主线程,在同一个Task中,遇到await,后面的任务将会被挂起,等到await任务执行完后,会回到被挂起的地方继续执行。这样就做到了 异步串行。

    异步并发 async-let

    async let 和 let 类似,它定义一个本地常量,并通过等号右侧的表达式来初始化这个常量。区别在于,这个初始化表达式必须是一个异步函数的调用,通过将这个异步函数“绑定”到常量值上,Swift 会创建一个并发执行的子任务,并在其中执行该异步函数。async let 赋值后,子任务会立即开始执行。如果想要获取执行的结果 (也就是子任务的返回值),可以对赋值的常量使用 await 等待它的完成。

    当 v0 任务完成后,它的结果将被暂存在它自身的续体栈上,等待执行上下文通过 await 切换到自己时,才会把结果返回。

    如果没有 await,那么 Swift 并发会在被绑定的常量离开作用域时,隐式地将绑定的子任务取消掉,然后进行 await。

    class ViewController: UIViewController {
        @IBOutlet weak var imageView: UIImageView!
    
        @IBAction func syncConcurrent(_ sender: UIControl) {
            Task {
                let image = try await downloadImageThumbnail(id: 1+arc4random()%20)
                self.imageView.image = image
            }
        }
        
        enum ThumbnailError: Error {
            case badImage
        }
        
        func downloadImageThumbnail(id: UInt32) async throws -> UIImage {
            try Task.checkCancellation()
            
            async let image = downloadImage(id: id)
            async let metadata = downloadImageMetadata(id: id)
            guard let thumbnail = try await image.preparingThumbnail(of: try await metadata) else {
                throw ThumbnailError.badImage
            }
            return thumbnail
        }
    
        func downloadImage(id: UInt32) async throws -> UIImage {
            try Task.checkCancellation()
            
            print("started download image...")
            guard let aURL = URL(string: getOneImageUrl(id: id)) else {
                throw URLError(.badURL)
            }
            let request = URLRequest(url: aURL)
            let (data, _) = try await URLSession.shared.data(for: request)
            guard let image = UIImage(data: data) else {
                throw ThumbnailError.badImage
            }
            print("ended download image")
            return image
        }
        func downloadImageMetadata(id: UInt32) async throws -> CGSize {
            try Task.checkCancellation()
            
            print("started download image metadata...")
            let image = try await downloadImage(id: id)
            let height: CGFloat = 200
            let width = image.size.width/image.size.height * height
            print("ended download image metadata")
            return .init(width: width, height: height)
        }
        
        func getOneImageUrl(id: UInt32? = nil) -> String {
            let idx = id ?? 1 + arc4random() % 20
            return "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/HighResolution/\(idx).jpg"
        }
    }
    
    

    async let相当于对已存在的某个异步任务(方法)进行了二次封装,然后返回一个新的匿名异步任务,再将这个异步任务进行try await待其执行完成,就可使用结果值了。

    Group Tasks

    @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
    @frozen public struct ThrowingTaskGroup<ChildTaskResult, Failure> where Failure : Error {
    ...
    }
    
    • group 满足 AsyncSequence,因此我们可以使用 for await 的语法来获取子任务的执行结果。group 中的某个任务完成时,它的结果将被放到异步序列的缓冲区中。每当 group 的 next 会被调用时,如果缓冲区里有值,异步序列就将它作为下一个值给出;如果缓冲区为空,那么就等待下一个任务完成,这是异步序列的标准行为。
    • for await 的结束意味着异步序列的 next 方法返回了 nil,此时group 中的子任务已经全部执行完毕了,withTaskGroup 的闭包也来到最后。接下来,外层的 “End” 也会被输出。整个结构化并发结束执行。
    • 即使我们没有明确 await 任务组,编译器在检测到结构化并发作用域结束时,会为我们自动添加上 await 并在等待所有任务结束后再继续控制流:
    for i in 0 ..< 3 {
        group.addTask {
          await work(i)
        }
      }
    
    // 编译器自动生成的代码
      for await _ in group { }
    

    即使手动退出某个子任务的await行为,编译器也会自动加上如下的隐式操作:

    for await result in group {
        print("Get result: \(result)")
        // 在首个子任务完成后就跳出
        break
      }
      print("Task ended")
    
      // 编译器自动生成的代码
      await group.waitForAll()
    

    public mutating func addTask(priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> ChildTaskResult)

    • 注意addTask的operation是一个返回值类型为ChildTaskResult@Sendable的block,这意味在多个异步的task之间可以进行数据send达到线程通信的目的,也保证了数据访问的线程安全

    • 也可以使用addTaskUnlessCancelled() -> Bool这个方法,如果group外层的task被cancel了,则不会再addTask了:

      • Adds a child task to the group, unless the group has been canceled.
      • This method doesn't throw an error, even if the child task does.
    • group.cancelAll()取消全部任务

    func fetchThumbnails(ids: [UInt32]) async throws -> [UInt32: UIImage] {
            var result: [UInt32: UIImage] = [:]
            try await withThrowingTaskGroup(of: (UInt32, UIImage).self) { group in
                for id in ids {
                    group.addTask(priority: .medium) { [self] in
                        return (id, try await downloadImageThumbnail(id: id))
                    }
                }
                for try await (id, thumbnail) in group {
                    result[id] = thumbnail
                }
            }
            return result
        }
    
    • 调用
    @IBAction func btnTapped(_ sender: UIControl) {
            Task {
                let images = try await fetchThumbnails(ids: [1, 3, 5, 7])
                print("All thumbnail images downloaded")
                for (id,img) in images {
                    DispatchQueue.main.asyncAfter(deadline: .now() + Double(id), qos: .userInteractive) {
                        self.imageView.image = img
                    }
                }
            }
        }
    

    Unstructured Tasks

    如果将非结构化的异步方法调用和结构化的异步任务结合起来,可以利用Task{}包裹,并且将其存储,在合适的时机进行cancel和置nil

    @MainActor
    class MyDelegate: UICollectionViewDelegate {
        var thumbnailTasks: [IndexPath: Task<Void, Never>] = [:]
        
        func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
            let ids = getThumbnailIDs(for: item)
            thumbnailTasks[item] = Task {
                defer { thumbnailTasks[item] = nil }
                let thumbnails = await fetchThumbnails(for: ids)
                display(thumbnails, in: cell)
            }
        }
        
        func collectionView(_ view: UICollectionView, didEndDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
            thumbnailTasks[item]?.cancel()
        }
    }
    

    Unstructured Detached Tasks

    任务嵌套的异步子任务,可以通过group进行组合使其并发执行

    @MainActor
    class MyDelegate: UICollectionViewDelegate {
        var thumbnailTasks: [IndexPath: Task<Void, Never>] = [:]
        
        func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
            let ids = getThumbnailIDs(for: item)
            thumbnailTasks[item] = Task {
                defer { thumbnailTasks[item] = nil }
                let thumbnails = await fetchThumbnails(for: ids)
                Task.detached(priority: .background) {
                    withTaskGroup(of: Void.self) { g in
                        g.async { writeToLocalCache(thumbnails) }
                        g.async { log(thumbnails) }
                        g.async { ... }
                    }
                }
                display(thumbnails, in: cell)
            }
        }
    }
    

    异步计算属性

      var asyncCover: UIImage? {
            get async {
                return await getRemoteCoverImage()
            }
        }
        func getRemoteCoverImage() async -> UIImage? {
            //do some network requests
            return nil
        }
    

    异步函数

    在函数声明的返回箭头前面,加上 async 关键字,就可以把一个函数声明为异步函数:

    func loadSignature() async throws -> String {
      fatalError("暂未实现")
    }
    

    异步函数的 async 关键字会帮助编译器确保两件事情:

    • 它允许我们在函数体内部使用 await 关键字;
    • 它要求其他人在调用这个函数时,使用 await 关键字。

    这和与它处于类似位置的 throws 关键字有点相似。在使用 throws 时,它允许我们在函数内部使用 throw 抛出错误,并要求调用者使用 try 来处理可能的抛出。

    结构化并发

    • 基于 Task 的结构化并发模型

    在 Swift 并发编程中,结构化并发需要依赖异步函数,而异步函数又必须运行在某个任务上下文中,因此可以说,想要进行结构化并发,必须具有任务上下文。实际上,Swift 结构化并发就是以任务为基本要素进行组织的。

    • 当前任务状态
      Swift 并发编程把异步操作抽象为任务,在任意的异步函数中,我们总可是使用 withUnsafeCurrentTask 来获取和检查当前任务:
    func foo() async {
      withUnsafeCurrentTask { task in
        // 3
        if let task = task {
          // 4
          print("Cancelled: \(task.isCancelled)")
          // => Cancelled: false
    
          print(task.priority)
          // TaskPriority(rawValue: 33)
        } else {
          print("No task")
        }
      }
    }
    

    actor模型

    解决多线程数据访问安全问题,类似于lock的作用,保证了数据的安全访问

    actor Holder {
      var results: [String] = []
      func setResults(_ results: [String]) {
        self.results = results
      }
        
      func append(_ value: String) {
        results.append(value)
      }
    }
    

    actor 内部会提供一个隔离域:在 actor 内部对自身存储属性或其他方法的访问,比如在 append(_:) 函数中使用 results 时,可以不加任何限制,这些代码都会被自动隔离在被封装的“私有队列”里。但是从外部对 actor 的成员进行访问时,编译器会要求切换到 actor 的隔离域,以确保数据安全。在这个要求发生时,当前执行的程序可能会发生暂停。编译器将自动把要跨隔离域的函数转换为异步函数,并要求我们使用 await 来进行调用。

    • 调用:由于是以类似异步队列-线程的方式进行了内部封装/隔离,所以访问这些数据需要使用await标识,表示线程的调度
    // holder.setResults([])
    await holder.setResults([])
    
    // holder.append(data.appending(signature))
    await holder.append(data.appending(signature))
    
    // print("Done: \(holder.getResults())")
    print("Done: \(await holder.results)")
    

    当然,这种数据隔离只解决同时访问的造成的内存问题 (在 Swift 中,这种不安全行为大多数情况下表现为程序崩溃),并不会解决多个异步让数据增加/减少导致数据错乱不同步问题。

    我们可以使用 @MainActor 来确保 UI 线程的隔离。

    如果你是在一个没有“完全迁移”到 Swift Concurrency Safe 的项目的话,可能需要在 class 申明上也加上 @MainActor 来让它生效。

    另外,需要指出的是,@MainActor 需要 async 环境来完成 actor 的切换。

    Group抽象封装(简化)

    封装一个MyAsyncTaskGroup泛型化的group

    class MyAsyncTaskGroup<Data, ChildTaskResult> {
        typealias Operation = (Data) async throws -> (ChildTaskResult)
        
        let datas: [Data]
        let operation: Operation
        
        init(children datas: [Data], child operation: @escaping Operation) {
            self.datas = datas
            self.operation = operation
        }
        
        func throwingStart() async throws -> [ChildTaskResult] {
            var results: [ChildTaskResult] = []
            try await withThrowingTaskGroup(of: ChildTaskResult.self) { group in
                for data in datas {
                    group.addTask{ [self] in
                        try await self.operation(data)
                    }
                }
                for try await result in group {
                    results.append(result)
                }
            }
            return results
        }
    }
    
    • 自定义具体操作的group
    class AsyncImageDownloadGroup: MyAsyncTaskGroup<URL, UIImage> {
        let URLs: [URL]
        init(urls: [String]) {
            self.URLs = urls.compactMap{ .init(string: $0) }
            super.init(children: self.URLs) {
                try await API.shared.downloadImage(withURL: $0)
            }
        }
    }
    
    • 调用
    @IBAction func syncConcurrent(_ sender: UIControl) {
            Task {
                let images = try await AsyncImageDownloadGroup(
                    urls: [
                        API.shared.getOneImageUrl(),
                        API.shared.getOneImageUrl(),
                        API.shared.getOneImageUrl(),
                        API.shared.getOneImageUrl()
                    ]
                ).throwingStart()
                
                print("All thumbnail images downloaded")
                for (idx,img) in images.enumerated() {
                    DispatchQueue.main.asyncAfter(deadline: .now() + Double(idx), qos: .userInteractive) {
                        self.imageView.image = img
                    }
                }
            }
        }
    

    也可以这样使用:

    Task {
                let images = try await MyAsyncTaskGroup(
                    childrenDatas: [
                        API.shared.getOneImageUrl(),
                        API.shared.getOneImageUrl(),
                        API.shared.getOneImageUrl(),
                        API.shared.getOneImageUrl()
                    ].compactMap{ URL(string: $0) }, childOperation: {
                        try await API.shared.downloadImage(withURL: $0)
                    }
                ).throwingStart()
                
                print("All thumbnail images downloaded")
                for (idx,img) in images.enumerated() {
                    DispatchQueue.main.asyncAfter(deadline: .now() + Double(idx), qos: .userInteractive) {
                        self.imageView.image = img
                    }
                }
            }
    

    这样一封装,是不是感觉比系统原生的简洁多了。

    Actor Reentrancy

    核心代码

    do {
                let image = try await task.value
                cache[url] = .ready(image)
                return image
            } catch {
                cache[url] = nil
                throw error
            }
    
    • 此处执行reentrancy操作(等待task完成),目的是当downloadImage完成时,
    • 立即将对应的image进行缓存,并返回给外部调用者
    • 如果下载失败,则将对应的task存储置为nil
    actor ImageDownloader {
    
        private enum CacheEntry {
            case inProgress(Task<Image, Error>)
            case ready(Image)
        }
    
        private var cache: [URL: CacheEntry] = [:]
    
        func image(from url: URL) async throws -> Image? {
            if let cached = cache[url] {
                switch cached {
                case .ready(let image):
                    return image
                case .inProgress(let task):
                    return try await task.value
                }
            }
    
            let task = Task {
                try await downloadImage(from: url)
            }
    
            cache[url] = .inProgress(task)
    
            do {
                let image = try await task.value
                cache[url] = .ready(image)
                return image
            } catch {
                cache[url] = nil
                throw error
            }
        }
    }
    

    Actor Isolate

    actor LibraryAccount {
        let idNumber: Int
        var booksOnLoan: [Book] = []
    }
    
    extension LibraryAccount: Hashable {
        nonisolated func hash(into hasher: inout Hasher) {
            hasher.combine(idNumber)
        }
    }
    

    Sendable & @Sendable

    Sendable是一个协议,它标识的数据模型实例可以在actor的环境中被安全的访问

    struct Book: Sendable {
        var title: String
        var authors: [Author]
    }
    

    Sendable协议下的模型内部也要求所有自定义类型均实现Sendable协议,否则就会编译报错,但是我们可以实现一个类似于包裹器的泛型结构体Pair,让其实现Sendable协议,就可以了:

    struct Pair<T, U> {
        var first: T
        var second: U
    }
    
    extension Pair: Sendable where T: Sendable, U: Sendable {
    }
    

    @sendable可以标识一个func或closure的类型,表示自动实现Sendable协议

    public mutating func addTask(
      priority: TaskPriority? = nil, 
      operation: @escaping @Sendable () async throws -> ChildTaskResult
    )
    

    @MainActor

    该标识的方法,将会在主线程执行,但同样的也要在调用的地方用await标识

    @MainActor func checkedOut(_ booksOnLoan: [Book]) {
        booksView.checkedOutBooks = booksOnLoan
    }
    
    // Swift ensures that this code is always run on the main thread.
    await checkedOut(booksOnLoan)
    

    同样的,自定义类型也可以用@MainActor标识,表示其中的属性、方法等均在主线程执行,常用于UI类的标识。当然,如果用nonisolated标识某个方法、属性,表示其可以脱离于当前类型的main thread的context。

    @MainActor class MyViewController: UIViewController {
        func onPress(...) { ... } // implicitly @MainActor
    
    // 这个方法可以脱离主线程运行
        nonisolated func fetchLatestAndDisplay() async { ... } 
    }
    

    Async Sequence

    @main
    struct QuakesTool {
        static func main() async throws {
            let endpointURL = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.csv")!
    
            // skip the header line and iterate each one 
            // to extract the magnitude, time, latitude and longitude
            for try await event in endpointURL.lines.dropFirst() {
                let values = event.split(separator: ",")
                let time = values[0]
                let latitude = values[1]
                let longitude = values[2]
                let magnitude = values[4]
                print("Magnitude \(magnitude) on \(time) at \(latitude) \(longitude)")
            }
        }
    }
    

    上面的for-await-in类似于:

    var iterator = quakes.makeAsyncIterator()
    while let quake = await iterator.next() {
        if quake.magnitude > 3 {
            displaySignificantEarthquake(quake)
        }
    }
    

    上述for-try await-in可以正常配合breakcontinuedo{} catch{}使用

    Bytes from a FileHandle

    也可以用于以异步序列的方式读取本地/网络文件:

    let url = URL(fileURLWithPath: "/tmp/somefile.txt")
    for try await line in url.lines {
        ...
    }
    

    Bytes from a URLSession

    let (bytes, response) = try await URLSession.shared.bytes(from: url)
    
    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 /* OK */
    else {
        throw MyNetworkingError.invalidServerResponse
    }
    
    for try await byte in bytes {
        ...
    }
    

    Async Notifications

    let center = NotificationCenter.default
    let notification = await center.notifications(named: .NSPersistentStoreRemoteChange).first {
        $0.userInfo[NSStoreUUIDKey] == storeUUID
    }
    

    Custom AsyncSequence

    class QuakeMonitor {
        var quakeHandler: (Quake) -> Void
        func startMonitoring()
        func stopMonitoring()
    }
    
    let quakes = AsyncStream(Quake.self) { continuation in
        let monitor = QuakeMonitor()
        monitor.quakeHandler = { quake in
            continuation.yield(quake)
        }
        continuation.onTermination = { @Sendable _ in
            monitor.stopMonitoring()
        }
        monitor.startMonitoring()
    }
    
    let significantQuakes = quakes.filter { quake in
        quake.magnitude > 3
    }
    
    for await quake in significantQuakes {
        ...
    }
    

    对应的,也可以使用AsyncThrowingStream包裹对应的Sequence数据流。

    Continuation

    由于async/throws是需要await然后立即return对应的结果的,那么如果在一个新的async方法里,想要嵌套原有的基于handler的异步方法,那么就没法return了,因为在handler里边才能进行结果的对错校验。为了搭配这2种方式,引入了Continuation异步转发handler返回的数据,然后return

    问题:

    func asyncRequest<T>(_ req: URLRequest) async throws -> T {
            Network.request(req) { (error, data) in
                if let error = error {
                    throw error
                }
                else if let data = data {
                    // so how to `return` the data?
                }
            }
            
        }
        struct Network {
            static func request(_ req: URLRequest, completion: @escaping (Error?, Data?)->Void) {
                ...
            }
        }
    

    解决:

    func withCheckedThrowingContinuation<T>(
      function: String = #function, 
      _ body: (CheckedContinuation<T, Error>) -> Void
    ) async throws -> T
    
    // resume
    public func resume(returning x: T)
    public func resume(throwing x: E)
    

    CheckedContinuation<T, Error>

        func asyncRequest<T: Decodable>(_ req: URLRequest) async throws -> T {
            typealias RequestContinuation = CheckedContinuation<T, Error>
            return try await withCheckedThrowingContinuation{ (continuation: RequestContinuation) in
                Network.request(req) { (error, data: T?) -> Void in
                    if let error = error {
                        continuation.resume(throwing: error)
                    }
                    else if let data = data {
                        continuation.resume(returning: data)
                    }
                }
            }
            
        }
        struct Network {
            enum Error: Swift.Error {
                case noData
            }
            
            static func request<T: Decodable>(
                _ req: URLRequest,
                completion: @escaping (Swift.Error?, T?)->Void
            ) {
                let handler: (Foundation.Data?, URLResponse?, Swift.Error?) -> Void = { data,_,error in
                    if let error = error {
                        return completion(error, nil)
                    }
                    guard let data = data else {
                        return completion(Error.noData, nil)
                    }
                    do {
                        let object = try JSONDecoder().decode(T.self, from: data)
                        return completion(nil, object)
                    }
                    catch {
                        return completion(error, nil)
                    }
                }
                let task = URLSession.shared.dataTask(with: req, completionHandler: handler)
                task.resume()
            }
        }
    

    handler回调类似的,还有基于delegate的方式,同样可以使用Continuation来异步转发:

    class ViewController: UIViewController {
        private var activeContinuation: CheckedContinuation<[Post], Error>?
        func sharedPostsFromPeer() async throws -> [Post] {
            try await withCheckedThrowingContinuation { continuation in
                self.activeContinuation = continuation
                self.peerManager.syncSharedPosts()
            }
        }
    }
    
    extension ViewController: PeerSyncDelegate {
        func peerManager(_ manager: PeerManager, received posts: [Post]) {
            self.activeContinuation?.resume(returning: posts)
     // guard against multiple calls to resume
            self.activeContinuation = nil
        }
    
        func peerManager(_ manager: PeerManager, hadError error: Error) {
            self.activeContinuation?.resume(throwing: error)
     // guard against multiple calls to resume
            self.activeContinuation = nil
        }
    }
    
    

    参考

    相关文章

      网友评论

        本文标题:Swift 5.5 | Async throws/ Try aw

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