swift 如何实现发布服务器扩展,它会删除元素,直到与每个元素关联的异步任务完成

ki0zmccv  于 5个月前  发布在  Swift
关注(0)|答案(1)|浏览(54)

例如,有一个按钮,每次用户点击按钮,它都会触发一个任务(如网络请求,动画或IO)。
在此任务完成之前,按钮应忽略任何单击事件,以避免多次运行。
换句话说,我想要一个发布者扩展方法,它删除发布者发出的元素,直到与每个元素关联的异步任务完成。
我实现了Publisher的这个扩展。它好吗?有更好的解决方案吗?

import UIKit
import Combine

class SwiftViewController: UIViewController {
    
    let clickedSubject = PassthroughSubject<(), Never>()
    
    var cancellables = Set<AnyCancellable>()
        
    override func viewDidLoad() {
        super.viewDidLoad()
                
//        clickedSubject.flatMapAndDropUntilLastCompleted { () in
//            print("clicked")
//            return Future<Int, Never>.init { promise in
//                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
//                    promise(Result.success(Int.random(in: 0...100)))
//                }
//            }.eraseToAnyPublisher()
//        }.sink { data in
//            print("finish \(data)")
//        }.store(in: &cancellables)
        
        clickedSubject.dropUntilTaskCompleted({ _ in
            print("clicked")
            try! await Task.sleep(nanoseconds: 1_000_000_000)
            return Int.random(in: 0...100)
        }).sink { data in
            print("finish \(data)")
        }.store(in: &cancellables)
        
        let button = UIButton(type: .close)
        view.addSubview(button)
        button.center = view.center
        button.addTarget(self, action: #selector(clicked), for: .touchUpInside)
    }

    @objc func clicked() {
        clickedSubject.send(())
    }
}

extension Publisher {
    
    /// Applies a transformation to each element emitted by the publisher, but drops all subsequent elements until the last transformation completes.
    /// - Parameter transform: A closure that takes an element emitted by the publisher and returns a new publisher.
    /// - Returns: A publisher that emits elements from the last completed transformation.
    public func flatMapAndDropUntilLastCompleted<P>(_ transform: @escaping (Self.Output) -> P) -> AnyPublisher<P.Output, P.Failure> where P: Publisher, Self.Failure == P.Failure {
        var drop = false
        return self.flatMap { output in
            if drop {
                return Empty<P.Output, P.Failure>().eraseToAnyPublisher()
            } else {
                drop = true
                return transform(output).handleEvents(receiveCompletion: { _ in
                    drop = false
                }).eraseToAnyPublisher()
            }
        }.eraseToAnyPublisher()
    }
    
    
    ///  Drops elements emitted by the publisher until the asynchronous task associated with each element is completed.
    /// - Parameter task: A closure that takes an element emitted by the publisher and returns an asynchronous task.
    /// - Returns: A publisher that emits the results of the completed tasks.
    public func dropUntilTaskCompleted<T>(_ task: @escaping (Self.Output) async -> T) -> AnyPublisher<T, Self.Failure> {
        flatMapAndDropUntilLastCompleted { output in
            Future { promise in
                Task {
                    let result = await task(output)
                    promise(.success(result))
                }
            }
        }
    }
}

字符串
我提出这个问题是因为有一个共同的用例。
单击按钮从磁盘读取一些数据,然后导航到包含数据的新页面。
由于从磁盘阅读数据是无效的,因此不会立即推送到新页。
在这种情况下,如果用户快速连续地点击按钮,它可能会多次按下。
不仅是从磁盘上阅读数据。只要导航不同步。问题就在这里。

erhoui1w

erhoui1w1#

还有一种方法可以解决你的问题。你可以限制异步任务每次最多运行一个,而不是控制异步任务的触发器(在你的例子中是按钮点击/单击)。
在网络请求这样的场景中,您希望额外使用.share()运算符(Apple doc)。
与多个订阅服务器共享上游发布服务器的输出。

let request = URLRequest(url: url)
let publisher = URLSession.shared.dataTaskPublisher(for: request)
    .shared()

字符串
现在,每次您在按钮操作后订阅publisher时,如果请求已经发生,则由于.share(),它将“附加”到已经存在的正在进行的操作。
这有额外的好处,因为你从按钮中抽象出来,你可以使用相同的publisher.share()示例来控制按钮和任何其他订阅者(例如,其他触发器不一定是按钮),不要对某些特定的网络操作发出多余的请求,只要共享响应在你的逻辑中是有效的。

相关问题