如何模拟completionHandler风格的代码,可以立即返回和/或通过async/await异步返回?

问题描述 投票:0回答:1

我试图了解如何更好地使用 async/await 和新的 Swift 并发性。我很久以前就学会了如何使用 Objective-C 编写 iOS 应用程序,并且仍然以该参考框架为基础进行研究。

使用传统的完成处理程序风格逻辑,您可以编写一个图像获取器函数,您可以在其中检查缓存,如果图像存在,则立即执行完成处理程序以返回图像。如果图像不存在,您可以调用某个后台线程来执行获取它所需的任何工作(磁盘、网络等),然后稍后在主线程上返回完成处理程序。当您想要立即使用 UIImage 而不是在下一个运行循环中以避免图像加载延迟或导致闪烁时(例如,在 UICollectionViewCell 中设置图像时),这非常有用

func fetchImage(url: URL, completion: ((UIImage) -> Void)) {
    if let image = self.images[url] {
        completion(image)
        return
    }

    DispatchQueue.global(qos: .background).async {
        // Code to do the network fetch or whatever
        DispatchQueue.main.async {
            completion(...)
        }
    }
}

// On the main thread somewhere else
fetchImage(url: url) { image in
    cell.imageView.image = image
}

我不确定如何使用 async/await 执行相同的行为,而无需单独执行非异步调用来检查缓存。

func fetchImageSync(url: Url) -> UIImage? {
    return self.images[url]
}

func fetchImageAsync(url: Url) async -> UIImage {
    if let image = self.images[url] {
        return image
    }

    // Do some async stuff to get the image
    self.images[url] = image
    return image
}

// On the main thread
if let image = fetchImageSync(url: url) {
    cell.imageView.image = image
} else {
    Task {
        if image = await fetchImageAsync(url: url) {
            await MainActor.run {
                  cell.imageView.image = image
            }
        }
    }
}

上述方法的缺点是无法在图像已存储在缓存中的快乐路径中的主线程上执行同步代码。

swift
1个回答
0
投票

为了采用 Swift 并发编程风格,最好考虑一切都是异步的。

此外,不再有“线程”的概念。 Swift 并发中存在“主要参与者”或

@MainThread
的概念,因为依赖关系仅限于主线程。例如,UIKit 希望在主线程上调用。现代 Swift 并发性提供了一种处理这些“遗留”组件的方法。

借助 Swift Concurrency,大多数编程挑战变得更加简单:

从图像获取器功能开始。

func fetchImage(url: URL) async throws { ... }

注意,该函数是异步的,可能会失败。这是呼叫者需要知道的全部内容。特别是,调用者不关心线程或完成处理程序。

声明异步函数的事实

async
并不意味着它必须暂停。立即返回就可以了:

func fetchImage(url: URL) async throws { if let image = Cache.getImage(with: url) { return image } // fetch image asynchronously from service ... }
因此,即使函数被声明为

async

,它也
可以同步返回。

假设你添加了从服务器异步获取图像的代码,上面的代码就够了。

注意:当你想从另一个函数调用上面的

fetchImage(url:)

函数时,这个调用函数本身必须是异步的(“一切都是异步的”):

func foo() throws async { ... let image = try await fetchImage(url: url) ... }
为了将此代码集成到“旧版”UIKit 中,您可以这样做:

您的

UIViewController

 子类的方法,当图像 URL 更改或初始化时将调用该方法:

private func onChangeOfImageUrl(url: URL) { Task { @MainActor in do { let image = try await fetchImage(url: url) self.update(image: image) } catch { self.handleError(error) } } }
旁注:

onChangeOfImageUrl(url:)

原样可以从任何线程调用,但很可能我们是从 UIKit 代码中调用它 - 因此我们可以假设我们正在主线程上运行。如果我们做出这个假设,最佳实践就是将其明确化,例如assert(Thread.isMainThread)

函数

update(image:)

是一个在主线程上运行的方法,用于处理视图的更新。

© www.soinside.com 2019 - 2024. All rights reserved.