英文:
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论