我需要处理一个异步任务结果.问题是,如果我在它正在进行的时候调用它,我需要更新完成.

GCD年后,它会看起来像这样:

func someFunc(completion: (() -> ())?) {
        if isLoading {
            if let completion {
                delayedCompletion = completion
            }
            return
        }
        isLoading = true
        delayedCompletion = completion

        //example of some async task
        DispatchQueue.main.async { [weak self] in
            self?.delayedContinuation?()
            self?.delayedContinuation = nil
        }
    }

但如何做到这一点与async/await?try 编写代码:

func someFunc() async {
        if isLoading {
            return await withCheckedContinuation { [weak self] checkedContinuation in
                self?.delayedContinuation = checkedContinuation
            }
        }
        isLoading = true
        return await withCheckedContinuation { [weak self] checkedContinuation in
            self?.delayedContinuation = checkedContinuation
            Task { @MainActor [weak self] in
                self?.delayedContinuation?.resume()
            }
        }
    }

这是正确的,还是有其他方法来添加一个不同的completion块?

推荐答案

有几种基本模式:

  1. 等待优先任务.

    actor AdManager {
        var inProgressTask: Task<Void, Error>? // if you don’t `try` anything inside the `Task {…}`, this property would be `Task<Void, Never>?` 
    
        func nextAdWithWait() async throws {
            if let inProgressTask {
                try await inProgressTask.value
                return
            }
    
            let task = Task {
                defer { inProgressTask = nil }
    
                try await fetchAndPresentAd()
            }
            inProgressTask = task
    
            // note, because this is unstructured concurrency, we want to manually handle cancelation
    
            try await withTaskCancellationHandler {
                try await task.value
            } onCancel: {
                task.cancel()
            }
        }
    }
    
  2. 取消上一个任务并启动一个新任务.

    func nextAdWithCancelPrevious() async throws {
        inProgressTask?.cancel()
    
        let task = Task {
            defer { inProgressTask = nil }
    
            try await fetchAndPresentAd()
        }
        inProgressTask = task
    
        try await withTaskCancellationHandler {
            try await task.value
        } onCancel: {
            task.cancel()
        }
    }
    

在展示了几个基本模式之后,您可能希望获取广告并在UI中呈现它们,因此您希望将获取与UI中的呈现分离.

人们可能会从某个"广告管理器"生成一个广告的异步序列,并在获取广告时产生价值.因此,用户界面可以启动"定期获取广告",然后在广告进入时对其进行处理.

actor AdManager {
    /// Generate sequence of ads

    func ads(durationBetweenAds: Duration = .seconds(60)) -> AsyncStream<Ad> {
        AsyncStream { continuation in
            let task = Task {
                defer { continuation.finish() }
                
                while !Task.isCancelled {
                    if let ad = await nextAd() {
                        continuation.yield(ad)
                        try? await Task.sleep(for: durationBetweenAds)
                    } else {
                        try? await Task.sleep(for: .seconds(10)) // in case ad server is down or broken, don't flood it with requests (but, at the same time, maybe not wait a full minute before you try again)
                    }
                }
            }
            
            continuation.onTermination = { _ in
                task.cancel()
            }
        }
    }
    
    func nextAd() async -> AdManager.Ad? {…}
}

extension AdManager {
    /// model structure for a given ad … perhaps your ad platform already has a model object for this
    
    struct Ad {…}
}

然后,用户界面可以监控该异步序列.例如,在UIKit中:

class ViewController: UIViewController {
    private var adsTask: Task<Void, Never>?
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        adsTask = Task { await showAds() }
    }    
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        adsTask?.cancel()
    }
    
    func showAds() async {
        let adManager = await AdManager()
        let ads = await adManager.ads()
        
        for await ad in ads {
            await showAdInUI(ad)
        }
    }

    func showAdInUI(_ ad: AdManager.Ad) async {…}
}

在SwiftUI中,您不需要这种非 struct 化并发.只需直接await.task视图修改器中的showAds函数,它将在视图出现时启动它,并在视图消失时自动取消它.但是,在UIKit中,我们需要手动处理取消,如上所述.

现在,你还没有分享你的广告框架,所以上面的许多细节可能会有所不同.但不要迷失在细节中.SWIFT并发的基本思想是,您可能想要一个异步的广告序列,然后让UI迭代该序列,并在它们进入时呈现它们.这是使用异步事件序列的自然SWIFT并发模式.

Swift相关问答推荐

如何获取SCNCamera正在查看的网格点(SCNNode)的局部坐标和局部法线?

如何把多个可观测性变成一个完备性?

ARRaycast Query和前置摄像头(ARFaceTrackingConfiguration)

SWIFT异步/等待,多个监听程序

background Task(.backgroundTask)不适用于MySQL

当模式以编程方式关闭时在父视图控制器中触发操作

计算 CoreData 中所有唯一对象的数量?

在 Swift 中将异步任务包装到 DispatchWorkItem 中以使其可取消?

允许为 self 分配新的 struct 值的理由是什么?

URL appendPathComponent(_:) 已弃用?

如何测试可选 struct 的协议一致性

用逻辑运算符保护让

在 SwiftUI 中的 ForEach 中使用 id 时如何为数组设置动画索引

有什么方法可以快速为泛型参数分配默认值?

SwiftUI 文本被意外截断

如何防止 UITableViewCell 移动(Swift 5)

如何为多个类 Swift 进行扩展

自定义 MKAnnotation 标注视图?

TabView 在切换选项卡时重置导航堆栈

如何快速从另一个视图控制器重新加载表格视图