在iOS的Combine框架中,为发布者发出的值之间添加延迟。

huangapple go评论73阅读模式
英文:

Add delay between values emitted by publisher in Combine Framework in iOS

问题

我有一个发布者 let subject = PassthroughSubject<Human,Error>() 发出以下对象,

subject.send(Human(fName: "John", lName: "Abraham"))
subject.send(Human(fName: "Jane", lName: "Anne"))

我需要在接收这两个对象时之间有一段延迟。

example(of: "delayy(with:)") {
    
    struct Human {
        let fName: String
        let lName: String
    }

    let subject = PassthroughSubject<Human,Error>()
    let delayInSeconds = 4
    var isLoading = false
    
    subject
        .delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)
        subject.sink(
            receiveCompletion:{
                print("Received Completion", $0)
                isLoading = true
                print("isLoading =", isLoading)
            },
            receiveValue: {

                print("Value Received:" ,$0)
            })

        subject.send(Human(fName: "John", lName: "Abraham"))
        subject.send(Human(fName: "Jane", lName: "Anne"))
        subject.send(completion: .finished)
         
}

发布者将首先发出的值是 (fName: "John", lName: "Abraham")。第二个发出的值将是 (fName: "Jane", lName: "Anne")

我希望在这两个对象在 print("Value Received:" ,$0) 中接收时有一段延迟。

我尝试添加延迟,但似乎不起作用。感谢任何帮助。(我是一个初学者,对 Combine 不太熟悉)

注:上面的代码片段来自 Playground。

英文:

I have one publisher let subject = PassthroughSubject<Human,Error>() that emits the following objects,

subject.send(Human(fName: "John", lName: "Abraham"))
subject.send(Human(fName: "Jane", lName: "Anne"))

I need there to be a delay between these two objects, when they are being received.

example(of: "delayy(with:)"){
    
    struct Human {
        let fName: String
        let lName: String
    }

    let subject = PassthroughSubject<Human,Error>()
    let delayInSeconds = 4
    var isLoading = false
    
    subject
        .delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)
        subject.sink(
            receiveCompletion:{
                print("Received Completion", $0)
                isLoading = true
                print("isLoading =", isLoading)
            },
            receiveValue: {

                print("Value Received:" ,$0)
            })

        subject.send(Human(fName: "John", lName: "Abraham"))
        subject.send(Human(fName: "Jane", lName: "Anne"))
        subject.send(completion: .finished)
         
}

The First value the publisher is going to be emitting here is (fName: "John", lName: "Abraham"). The second value emitted is going to be (fName: "Jane", lName: "Anne").

I want there to be a delay between these two objects when the values are received here in print("Value Received:" ,$0)

I tried adding delay but it does not seem to be working. Any help is appreciated. (I am a beginner to combine)

fyi: The above code snippet is from playground

答案1

得分: 4

以下是翻译的内容:


你的代码存在一些问题。


你没有正确使用修饰符。

subject
    .delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)

subject.sink(

在这里,你将.delay修饰符分配给了你的subject。但这将返回一个新的发布者,你根本没有使用它。接下来的一行subject.sink会订阅原始发布者,没有延迟修饰符。

正确的语法是:

subject
    .delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)
    .sink(

此外,你没有存储由.sink修饰符返回的AnyCancellable。如果你不存储它,订阅将超出范围,你将无法接收新的值。

以下是正确的存储方式:

let cancellable = subject
                     .delay...
                     .sink...

如果你在函数或类中使用这个代码而不是在Playground中,要确保cancellable变量直到不再需要它才会超出范围。


解决了这些简单的问题后,让我们看看真正的问题。

.delay修饰符在这里无法帮助你。它只会延迟发出的所有值,延迟的时间由指定的值决定。这意味着,在发送值后,delay函数将等待指定的时间,然后一次性发出所有值。

我尝试了不同的方法,唯一有效的方法是结合使用.buffer.flatMap修饰符。

let cancellable = subject
    .buffer(size: 20000, prefetch: .byRequest, whenFull: .dropOldest)
    .flatMap(maxPublishers: .max(1)) { Just($0).delay(for: .seconds(delayInSeconds), scheduler: RunLoop.main) }
    .sink(
    receiveCompletion:{
        print("Received Completion", $0, Date())
        isLoading = true
        print("isLoading =", isLoading)
    },
    receiveValue: {
        print("Value Received:", $0, Date())
    })

缓冲区是必需的,用于存储值。.flatMap从给定的值创建新的发布者,但一次只发出一个值。然后,该发布者受到你提供的时间的延迟。

示例输入:

subject.send(Human(fName: "John", lName: "Abraham"))
subject.send(Human(fName: "Jane", lName: "Anne"))
subject.send(Human(fName: "John2", lName: "Abraham"))
subject.send(Human(fName: "John3", lName: "Abraham"))

subject.send(completion: .finished)

输出:

Value Received: Human(fName: "John", lName: "Abraham") 2023-02-10 10:58:48 +0000
Value Received: Human(fName: "Jane", lName: "Anne") 2023-02-10 10:58:52 +0000
Value Received: Human(fName: "John2", lName: "Abraham") 2023-02-10 10:58:56 +0000
Value Received: Human(fName: "John3", lName: "Abraham") 2023-02-10 10:59:00 +0000
Received Completion finished 2023-02-10 10:59:00 +0000
isLoading = true

这个解决方案有一些缺点。

  • 你需要显式指定缓冲区大小,以避免丢失值。
  • 完成操作立即在最后一个值之后触发(不知道这是否是一个问题)。
英文:

There are several issues with your code.


You do not use the modifier properly.

subject
    .delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)

subject.sink(

Here you assign the .delay modifier to your subject. But this will return an new publisher that you simply don´t use. With the next line subject.sink you are subscirbing to the original publisher without the delay modifier.

Proper syntax:

subject
    .delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)
    .sink(

Next, you are not storing the AnyCancellable that is returned by your .sink modifier. If you don´t store it, the subscription will go out of scope and you won´t receive new values.

let cancellable = subject
                     .delay...
                     .sink...

would be the proper way to store it. If you use this in a function or class instead of a Playground take care your cancellable var does not go out of scope untill you don´t need it anymore.


With these simple issues out of the way lets look at the real problem.

The .delay modifier won´t help you here. It just delays the entire stream of emitted values by the delay value given. This means, after sending your values the delay function will wait for the specified time and then emit all values at once.

I tried different approaches and the only thing that worked was a combination of a .buffer and a .flatMap modifier.

let cancellable = subject
    .buffer(size: 20000, prefetch: .byRequest, whenFull: .dropOldest)
    .flatMap(maxPublishers: .max(1)) { Just($0).delay(for: .seconds(delayInSeconds), scheduler: RunLoop.main) }
    .sink(
    receiveCompletion:{
        print("Received Completion", $0, Date())
        isLoading = true
        print("isLoading =", isLoading)
    },
    receiveValue: {
        print("Value Received:" ,$0, Date())
    })

The buffer is required to store the values somewhere. The .flatMap creates new publishers from the values given but emits only one at a time. This publisher is then delayed by the time you give.

Example input:

subject.send(Human(fName: "John", lName: "Abraham"))
subject.send(Human(fName: "Jane", lName: "Anne"))
subject.send(Human(fName: "John2", lName: "Abraham"))
subject.send(Human(fName: "John3", lName: "Abraham"))

subject.send(completion: .finished)

Output:

Value Received: Human(fName: "John", lName: "Abraham") 2023-02-10 10:58:48 +0000
Value Received: Human(fName: "Jane", lName: "Anne") 2023-02-10 10:58:52 +0000
Value Received: Human(fName: "John2", lName: "Abraham") 2023-02-10 10:58:56 +0000
Value Received: Human(fName: "John3", lName: "Abraham") 2023-02-10 10:59:00 +0000
Received Completion finished 2023-02-10 10:59:00 +0000
isLoading = true

This solution has some drawbacks.

  • you need to specify the buffer size explicitly in order to avoid losing values
  • the completion fires immediately after the last value (don´t know if this is an issue or not)

huangapple
  • 本文由 发表于 2023年2月10日 15:03:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75407880.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定