Wait combineLatest until #selector is called - swift

TL;DR;
I need to find out a way to setup combineLatest that processes events only after particular self.myMethod() is called without subscribing in that method.
Description
My component A has a subscribe() routin in init(), where all Rx subscriptions are set up.
import RxSwift
final class A {
let bag = DisposeBag()
init() {
//...
subscribe()
}
//...
private func subscribe() {
// Setup all Rx subscriptions here
}
There are two other dependencies B and C, each having their statuses that A needs to combineLatest and yield some UI Event upon that combination.
Observable.combineLatest(b.status,
c.status)
.filter { $0.0 == .connecting && $0.1 == .notReachable }
.map { _ -> Error in
return AError.noInternet
}
.debounce(RxTimeInterval.seconds(5), scheduler: MainScheduler.instance)
.subscribe(onNext: { [weak self] error in
self?.didFail(with: error)
})
.disposed(by: bag)
A is not a UI component and basically handles business logic, thus it should wait until UI "says" it is ready to handle that business logic. E.g., after myMethod() is called on A by UI layer.
Problem
I do want to have the Observable.combineLatest in subscribe() being setup in a way that waits until myMethod() is called and then immediately receives latest events from B's status and C's status.
Currently I do it this way in A:
public func myMethod()
// ...
Observable.combineLatest(...
}
, which breaks the clean code I am striving to.

One thing you could do is make the publisher connectable, and call .connect() when you need to:
let publisher: Publishers.MakeConnectable<AnyPublisher<YourOutput, YourError>>
func subscribe() {
publisher = Observable.combineLatest(b.status, c.status)
.filter { ... }
.map { ... }
.eraseToAnyPublisher()
.makeConnectable()
publisher.subscribe(...)
}
Then, in myMethod() you can do:
func myMethod() {
publisher.connect()
}

Another option would be to add a PublishSubject to your A class.
final class A {
let myMethodCalled = PublishSubject<Void>()
init() {
myMethodCalled
.withLatestFrom(Observable.combineLatest(a.status, b.status))
// etc...
}
func myMethod() {
myMethodCalled.onNext(())
}
}
The above might be a problem if, for example myMethod() is called before a.status and b.status emit any values though.
The best solution is to pass in an Observable that triggers the whole thing instead of calling myMethod(). Embrace the Rx paradigm and get rid of the passive (as opposed to reactive) myMethod().

Related

Swift Concurrency async/await equivalent of a dispatch barrier / semaphore [duplicate]

I've a document based application that uses a struct for its main data/model. As the model is a property of (a subclass of) NSDocument it needs to be accessed from the main thread. So far all good.
But some operations on the data can take quite a long time and I want to provide the user with a progress bar. And this is where to problems start. Especially when the user starts two operations from the GUI in quick succession.
If I run the operation on the model synchronously (or in a 'normal' Task {}) I get the correct serial behaviour, but the Main thread is blocked, hence I can't show a progress bar. (Option A)
If I run the operation on the model in a Task.detached {} closure I can update the progress bar, but depending on the run time of the operations on the model, the second action of the user might complete before the first operation, resulting in invalid/unexpected state of the model. This is due to the await statements needed in the detached task (I think). (Option B).
So I want a) to free up the main thread to update the GUI and b) make sure each task runs to full completion before another (queued) task starts. This would be quite possible using a background serial dispatch queue, but I'm trying to switch to the new Swift concurrency system, which is also used to perform any preparations before the model is accessed.
I tried using a global actor, as that seems to be some sort of serial background queue, but it also needs await statements. Although the likelihood of unexpected state in the model is reduced, it's still possible.
I've written some small code to demonstrate the problem:
The model:
struct Model {
var doneA = false
var doneB = false
mutating func updateA() {
Thread.sleep(forTimeInterval: 5)
doneA = true
}
mutating func updateB() {
Thread.sleep(forTimeInterval: 1)
doneB = true
}
}
And the document (leaving out standard NSDocument overrides):
#globalActor
struct ModelActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
class Document: NSDocument {
var model = Model() {
didSet {
Swift.print(model)
}
}
func update(model: Model) {
self.model = model
}
#ModelActor
func updateModel(with operation: (Model) -> Model) async {
var model = await self.model
model = operation(model)
await update(model: model)
}
#IBAction func operationA(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some A work...")
// self.model.updateA()
// }
//Option B
// Task.detached {
// Swift.print("Performing some A work...")
// var model = await self.model
// model.updateA()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some A work...")
await self.updateModel { model in
var model = model
model.updateA()
return model
}
}
}
#IBAction func operationB(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some B work...")
// self.model.updateB()
// }
//Option B
// Task.detached {
// Swift.print("Performing some B work...")
// var model = await self.model
// model.updateB()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some B work...")
await self.updateModel { model in
var model = model
model.updateB()
return model
}
}
}
}
Clicking 'Operation A' and then 'Operation B' should result in a model with two true's. But it doesn't always.
Is there a way to make sure that operation A completes before I get to operation B and have the Main thread available for GUI updates?
EDIT
Based on Rob's answer I came up with the following. I modified it this way because I can then wait on the created operation and report any error to the original caller. I thought it easier to comprehend what's happening by including all code inside a single update function, so I choose to go for a detached task instead of an actor. I also return the intermediate model from the task, as otherwise an old model might be used.
class Document {
func updateModel(operation: #escaping (Model) throws -> Model) async throws {
//Update the model in the background
let modelTask = Task.detached { [previousTask, model] () throws -> Model in
var model = model
//Check whether we're cancelled
try Task.checkCancellation()
//Check whether we need to wait on earlier task(s)
if let previousTask = previousTask {
//If the preceding task succeeds we use its model
do {
model = try await previousTask.value
} catch {
throw CancellationError()
}
}
return try operation(model)
}
previousTask = modelTask
defer { previousTask = nil } //Make sure a later task can always start if we throw
//Wait for the operation to finish and store the model
do {
self.model = try await modelTask.value
} catch {
if error is CancellationError { return }
else { throw error }
}
}
}
Call side:
#IBAction func operationA(_ sender: Any?) {
//Option D
Task {
do {
try await updateModel { model in
var model = model
model.updateA()
return model
}
} catch {
presentError(error)
}
}
}
It seems to do anything I need, which is queue'ing updates to a property on a document, which can be awaited for and have errors returned, much like if everything happened on the main thread.
The only drawback seems to be that on the call side the closure is very verbose due to the need to make the model a var and return it explicitly.
Obviously if your tasks do not have any await or other suspension points, you would just use an actor, and not make the method async, and it automatically will perform them sequentially.
But, when dealing with asynchronous actor methods, one must appreciate that actors are reentrant (see SE-0306: Actors - Actor Reentrancy). If you really are trying to a series of asynchronous tasks run serially, you will want to manually have each subsequent task await the prior one. E.g.,
actor Foo {
private var previousTask: Task<(), Error>?
func add(block: #Sendable #escaping () async throws -> Void) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
There are two subtle aspects to the above:
I use the capture list of [previousTask] to make sure to get a copy of the prior task.
I perform await previousTask?.value inside the new task, not before it.
If you await prior to creating the new task, you have race, where if you launch three tasks, both the second and the third will await the first task, i.e. the third task is not awaiting the second one.
And, perhaps needless to say, because this is within an actor, it avoids the need for detached task, while keeping the main thread free.

Make tasks in Swift concurrency run serially

I've a document based application that uses a struct for its main data/model. As the model is a property of (a subclass of) NSDocument it needs to be accessed from the main thread. So far all good.
But some operations on the data can take quite a long time and I want to provide the user with a progress bar. And this is where to problems start. Especially when the user starts two operations from the GUI in quick succession.
If I run the operation on the model synchronously (or in a 'normal' Task {}) I get the correct serial behaviour, but the Main thread is blocked, hence I can't show a progress bar. (Option A)
If I run the operation on the model in a Task.detached {} closure I can update the progress bar, but depending on the run time of the operations on the model, the second action of the user might complete before the first operation, resulting in invalid/unexpected state of the model. This is due to the await statements needed in the detached task (I think). (Option B).
So I want a) to free up the main thread to update the GUI and b) make sure each task runs to full completion before another (queued) task starts. This would be quite possible using a background serial dispatch queue, but I'm trying to switch to the new Swift concurrency system, which is also used to perform any preparations before the model is accessed.
I tried using a global actor, as that seems to be some sort of serial background queue, but it also needs await statements. Although the likelihood of unexpected state in the model is reduced, it's still possible.
I've written some small code to demonstrate the problem:
The model:
struct Model {
var doneA = false
var doneB = false
mutating func updateA() {
Thread.sleep(forTimeInterval: 5)
doneA = true
}
mutating func updateB() {
Thread.sleep(forTimeInterval: 1)
doneB = true
}
}
And the document (leaving out standard NSDocument overrides):
#globalActor
struct ModelActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
class Document: NSDocument {
var model = Model() {
didSet {
Swift.print(model)
}
}
func update(model: Model) {
self.model = model
}
#ModelActor
func updateModel(with operation: (Model) -> Model) async {
var model = await self.model
model = operation(model)
await update(model: model)
}
#IBAction func operationA(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some A work...")
// self.model.updateA()
// }
//Option B
// Task.detached {
// Swift.print("Performing some A work...")
// var model = await self.model
// model.updateA()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some A work...")
await self.updateModel { model in
var model = model
model.updateA()
return model
}
}
}
#IBAction func operationB(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some B work...")
// self.model.updateB()
// }
//Option B
// Task.detached {
// Swift.print("Performing some B work...")
// var model = await self.model
// model.updateB()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some B work...")
await self.updateModel { model in
var model = model
model.updateB()
return model
}
}
}
}
Clicking 'Operation A' and then 'Operation B' should result in a model with two true's. But it doesn't always.
Is there a way to make sure that operation A completes before I get to operation B and have the Main thread available for GUI updates?
EDIT
Based on Rob's answer I came up with the following. I modified it this way because I can then wait on the created operation and report any error to the original caller. I thought it easier to comprehend what's happening by including all code inside a single update function, so I choose to go for a detached task instead of an actor. I also return the intermediate model from the task, as otherwise an old model might be used.
class Document {
func updateModel(operation: #escaping (Model) throws -> Model) async throws {
//Update the model in the background
let modelTask = Task.detached { [previousTask, model] () throws -> Model in
var model = model
//Check whether we're cancelled
try Task.checkCancellation()
//Check whether we need to wait on earlier task(s)
if let previousTask = previousTask {
//If the preceding task succeeds we use its model
do {
model = try await previousTask.value
} catch {
throw CancellationError()
}
}
return try operation(model)
}
previousTask = modelTask
defer { previousTask = nil } //Make sure a later task can always start if we throw
//Wait for the operation to finish and store the model
do {
self.model = try await modelTask.value
} catch {
if error is CancellationError { return }
else { throw error }
}
}
}
Call side:
#IBAction func operationA(_ sender: Any?) {
//Option D
Task {
do {
try await updateModel { model in
var model = model
model.updateA()
return model
}
} catch {
presentError(error)
}
}
}
It seems to do anything I need, which is queue'ing updates to a property on a document, which can be awaited for and have errors returned, much like if everything happened on the main thread.
The only drawback seems to be that on the call side the closure is very verbose due to the need to make the model a var and return it explicitly.
Obviously if your tasks do not have any await or other suspension points, you would just use an actor, and not make the method async, and it automatically will perform them sequentially.
But, when dealing with asynchronous actor methods, one must appreciate that actors are reentrant (see SE-0306: Actors - Actor Reentrancy). If you really are trying to a series of asynchronous tasks run serially, you will want to manually have each subsequent task await the prior one. E.g.,
actor Foo {
private var previousTask: Task<(), Error>?
func add(block: #Sendable #escaping () async throws -> Void) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
There are two subtle aspects to the above:
I use the capture list of [previousTask] to make sure to get a copy of the prior task.
I perform await previousTask?.value inside the new task, not before it.
If you await prior to creating the new task, you have race, where if you launch three tasks, both the second and the third will await the first task, i.e. the third task is not awaiting the second one.
And, perhaps needless to say, because this is within an actor, it avoids the need for detached task, while keeping the main thread free.

Rxswift Map and Zip not Called

I am trying to get the element of 2 observables produced asynchronously and pass them as parameters to a function once both are received.
However my map operator in my ViewModel below is not executed and the breakpoint just skips over it.
ViewModel.swift
init(api: ApiService) {
self.api = api
}
func getData1() -> Observable<Data1> {
return api.getData1()
}
func getData2() -> Observable<NewViewModel> {
return Observable.create { observer in
let disposable = Disposables.create()
self.api.getData2()
.map {
$0.arrayOfStuff.forEach { (stuff) in
let background = stuff.background
let newViewModel = NewViewModel( background: self.spotlightBackground)
observor.onNext(newViewModel)
}
return disposable
}
}
In my ViewController i am creating the Zip of the observables because newViewModel[getData2] may return later and i want to call the function when both observables emit a value
in my viewDidLoad() i setup zip by subscribing and adding observables
let zippy = Observable.zip(viewModel.getData1(), viewModel.getData2()).subscribe(onNext: { (data1, newViewModel) in
self.layoutSetUp(data1: data1, newViewModel: newViewModel)
})
zippy.disposed(by: disposeBag)
private func layoutSetUp(data1: Data1, newViewModel: NewViewModel) {
DispatchQueue.main.async {
self.view = SwiftUIHostingView(rootView: SwiftUIContentView(data1: data1, newViewModel: newViewModel))
}
}
This is not executing and no values are passed to function either and im not sure why
Your getData2 method never emits a value so neither will the zip. The code in the method is a bit too muddled for me to understand what you are trying to do so I can't tell you exactly what you need, but I can say that when you have an observable that nothing is subscribed to, then it will not emit a value.
This bit:
self.api.getData2()
.map {
$0.arrayOfStuff.forEach { (stuff) in
let background = stuff.background
let newViewModel = NewViewModel(background: self.spotlightBackground)
observor.onNext(newViewModel)
}
return disposable
}
Is an observable with no subscribers.

How to replicate PromiseKit-style chained async flow using Combine + Swift

I was using PromiseKit successfully in a project until Xcode 11 betas broke PK v7. In an effort to reduce external dependencies, I decided to scrap PromiseKit. The best replacement for handling chained async code seemed to be Futures using the new Combine framework.
I am struggling to replicate the simple PK syntax using Combine
ex. simple PromiseKit chained async call syntax
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.then{popToRootViewController}.catch{handleError(error)}
I understand:
A Swift standard library implementation of async/await would solve this problem (async/await does not yet exist, despite lots of chatter and involvement from Chris Latter himself)
I could replicate using Semaphores (error-prone?)
flatMap can be used to chain Futures
The async code I'd like should be able to be called on demand, since it's involved with ensuring user is logged in. I'm wrestling with two conceptual problems.
If I wrap Futures in a method, with sink to handle result, it seems that the method goes out of scope before subscriber is called by sink.
Since Futures execute only once, I worry that if I call the method multiple times I'll only get the old, stale, result from the first call. To work around this, maybe I would use a PassthroughSubject? This allows the Publisher to be called on demand.
Questions:
Do I have to retain every publisher and subscriber outside of the
calling method
How can I replicate simple chained async using the Swift standard library and then embed this in a swift instance method I can call on-demand to restart the chained async calls from the top??
//how is this done using Combine?
func startSync() {
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.catch{\\handle error here}
}
This is not a real answer to your whole question — only to the part about how to get started with Combine. I'll demonstrate how to chain two asynchronous operations using the Combine framework:
print("start")
Future<Bool,Error> { promise in
delay(3) {
promise(.success(true))
}
}
.handleEvents(receiveOutput: {_ in print("finished 1")})
.flatMap {_ in
Future<Bool,Error> { promise in
delay(3) {
promise(.success(true))
}
}
}
.handleEvents(receiveOutput: {_ in print("finished 2")})
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
First of all, the answer to your question about persistence is: the final subscriber must persist, and the way to do this is using the .store method. Typically you'll have a Set<AnyCancellable> as a property, as here, and you'll just call .store as the last thing in the pipeline to put your subscriber in there.
Next, in this pipeline I'm using .handleEvents just to give myself some printout as the pipeline moves along. Those are just diagnostics and wouldn't exist in a real implementation. All the print statements are purely so we can talk about what's happening here.
So what does happen?
start
finished 1 // 3 seconds later
finished 2 // 3 seconds later
done
So you can see we've chained two asynchronous operations, each of which takes 3 seconds.
How did we do it? We started with a Future, which must call its incoming promise method with a Result as a completion handler when it finishes. After that, we used .flatMap to produce another Future and put it into operation, doing the same thing again.
So the result is not beautiful (like PromiseKit) but it is a chain of async operations.
Before Combine, we'd have probably have done this with some sort of Operation / OperationQueue dependency, which would work fine but would have even less of the direct legibility of PromiseKit.
Slightly more realistic
Having said all that, here's a slightly more realistic rewrite:
var storage = Set<AnyCancellable>()
func async1(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async1")
promise(.success(true))
}
}
func async2(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async2")
promise(.success(true))
}
}
override func viewDidLoad() {
print("start")
Future<Bool,Error> { promise in
self.async1(promise)
}
.flatMap {_ in
Future<Bool,Error> { promise in
self.async2(promise)
}
}
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
As you can see, the idea that is our Future publishers simply have to pass on the promise callback; they don't actually have to be the ones who call them. A promise callback can thus be called anywhere, and we won't proceed until then.
You can thus readily see how to replace the artificial delay with a real asynchronous operation that somehow has hold of this promise callback and can call it when it completes. Also my promise Result types are purely artificial, but again you can see how they might be used to communicate something meaningful down the pipeline. When I say promise(.success(true)), that causes true to pop out the end of the pipeline; we are disregarding that here, but it could be instead a downright useful value of some sort, possibly even the next Future.
(Note also that we could insert .receive(on: DispatchQueue.main) at any point in the chain to ensure that what follows immediately is started on the main thread.)
Slightly neater
It also occurs to me that we could make the syntax neater, perhaps a little closer to PromiseKit's lovely simple chain, by moving our Future publishers off into constants. If you do that, though, you should probably wrap them in Deferred publishers to prevent premature evaluation. So for example:
var storage = Set<AnyCancellable>()
func async1(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async1")
promise(.success(true))
}
}
func async2(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async2")
promise(.success(true))
}
}
override func viewDidLoad() {
print("start")
let f1 = Deferred{Future<Bool,Error> { promise in
self.async1(promise)
}}
let f2 = Deferred{Future<Bool,Error> { promise in
self.async2(promise)
}}
// this is now extremely neat-looking
f1.flatMap {_ in f2 }
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
matt's answer is correct, use flatMap to chain promises. I got in the habit of returning promises when using PromiseKit, and carried it over to Combine (returning Futures).
I find it makes the code easier to read. Here's matt's last example with that recommendation:
var storage = Set<AnyCancellable>()
func async1() -> Future<Bool, Error> {
Future { promise in
delay(3) {
print("async1")
promise(.success(true))
}
}
}
func async2() -> Future<Bool, Error> {
Future { promise in
delay(3) {
print("async2")
promise(.success(true))
}
}
}
override func viewDidLoad() {
print("start")
async1()
.flatMap { _ in async2() }
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
Note that AnyPublisher will work as a return value as well, so you could abstract away the Future and have it return AnyPublisher<Bool, Error> instead:
func async2() -> AnyPublisher<Bool, Error> {
Future { promise in
delay(3) {
print("async2")
promise(.success(true))
}
}.eraseToAnyPubilsher()
}
Also if you want to use the PromiseKit-like syntax, here are some extensions for Publisher
I am using this to seamlessly switch from PromiseKit to Combine in a project
extension Publisher {
func then<T: Publisher>(_ closure: #escaping (Output) -> T) -> Publishers.FlatMap<T, Self>
where T.Failure == Self.Failure {
flatMap(closure)
}
func asVoid() -> Future<Void, Error> {
return Future<Void, Error> { promise in
let box = Box()
let cancellable = self.sink { completion in
if case .failure(let error) = completion {
promise(.failure(error))
} else if case .finished = completion {
box.cancellable = nil
}
} receiveValue: { value in
promise(.success(()))
}
box.cancellable = cancellable
}
}
#discardableResult
func done(_ handler: #escaping (Output) -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: {compl in
if case .finished = compl {
box.cancellable = nil
}
}, receiveValue: {
handler($0)
})
box.cancellable = cancellable
return self
}
#discardableResult
func `catch`(_ handler: #escaping (Failure) -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: { compl in
if case .failure(let failure) = compl {
handler(failure)
} else if case .finished = compl {
box.cancellable = nil
}
}, receiveValue: { _ in })
box.cancellable = cancellable
return self
}
#discardableResult
func finally(_ handler: #escaping () -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: { compl in
if case .finished = compl {
handler()
box.cancellable = nil
}
}, receiveValue: { _ in })
box.cancellable = cancellable
return self
}
}
fileprivate class Box {
var cancellable: AnyCancellable?
}
And here's an example of use:
func someSync() {
Future<Bool, Error> { promise in
delay(3) {
promise(.success(true))
}
}
.then { result in
Future<String, Error> { promise in
promise(.success("111"))
}
}
.done { string in
print(string)
}
.catch { err in
print(err.localizedDescription)
}
.finally {
print("Finished chain")
}
}
You can use this framework for Swift coroutines, it's also can be used with Combine - https://github.com/belozierov/SwiftCoroutine
DispatchQueue.main.startCoroutine {
let future: Future<Bool, Error>
let coFuture = future.subscribeCoFuture()
let bool = try coFuture.await()
}

Swift Combine alternative to Rx Observable.create

I have some code that is built using RxSwift, and I'm playing around with converting it to use Apple's Combine framework.
One pattern which is very common is the use of Observable.create for one-shot observables (usually network requests). Something like this:
func loadWidgets() -> Observable<[Widget]> {
return Observable.create { observer in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
observer.onNext(widgets)
observer.onComplete()
}, error: { error in
// publish error on failure
observer.onError()
})
// allow cancellation
return Disposable {
loadTask.cancel()
}
}
}
I'm trying to map that across to Combine and I haven't been able to quite figure it out. The closest I've been able to get is using Future for something like this:
func loadWidgets() -> AnyPublisher<[Widget], Error> {
return Future<[Widget], Error> { resolve in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
resolve(.success(widgets))
}, error: { error in
// publish error on failure
resolve(.failure(error))
})
// allow cancellation ???
}
}
As you can see, it does most of it, but there's no ability to cancel.
Secondarily, future doesn't allow multiple results.
Is there any way to do something like the Rx Observable.create pattern which allows cancellation and optionally multiple results?
I think I found a way to mimic Observable.create using a PassthroughSubject in Combine. Here is the helper I made:
struct AnyObserver<Output, Failure: Error> {
let onNext: ((Output) -> Void)
let onError: ((Failure) -> Void)
let onComplete: (() -> Void)
}
struct Disposable {
let dispose: () -> Void
}
extension AnyPublisher {
static func create(subscribe: #escaping (AnyObserver<Output, Failure>) -> Disposable) -> Self {
let subject = PassthroughSubject<Output, Failure>()
var disposable: Disposable?
return subject
.handleEvents(receiveSubscription: { subscription in
disposable = subscribe(AnyObserver(
onNext: { output in subject.send(output) },
onError: { failure in subject.send(completion: .failure(failure)) },
onComplete: { subject.send(completion: .finished) }
))
}, receiveCancel: { disposable?.dispose() })
.eraseToAnyPublisher()
}
}
And here is how it looks in usage:
func loadWidgets() -> AnyPublisher<[Widget], Error> {
AnyPublisher.create { observer in
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
observer.onNext(widgets)
observer.onComplete()
}, error: { error in
observer.onError(error)
})
return Disposable {
loadTask.cancel()
}
}
}
From what I've learned, the support for initializing an AnyPublisher with a closure has been dropped in Xcode 11 beta 3. This would be a corresponding solution for Rx's Observable.create in this case, but for now I believe that the Future is a goto solution if you only need to propagate single value. In other cases I would go for returning a PassthroughSubject and propagating multiple values this way, but it will not allow you to start a task when the observation starts and I believe it's far from ideal compared to Observable.create.
In terms of cancellation, it does not have an isDisposed property similar to a Disposable, so it's not possible to directly check the state of it and stop your own tasks from executing. The only way that I can think of right now would be to observe for a cancel event, but it's surely not as comfortable as a Disposable.
Also, I'd assume that cancel might in fact stop tasks like network requests from URLSession based on the docs here: https://developer.apple.com/documentation/combine/cancellable
Add an isCancelled operation outside the closure and check it in the future's closure. isCancelled can be toggled with the handleEvent() operator.
var isCancelled = false
func loadWidgets() -> AnyPublisher<[Widget], Error> {
return HandleEvents<Future<Any, Error>> { resolve in
// start the request when someone subscribes
let loadTask = WidgetLoader.request("allWidgets", completion: { widgets in
// publish result on success
resolve(.success(widgets))
}, error: { error in
// publish error on failure
resolve(.failure(error))
}
if isCancelled {
loadTask.cancel()
}
).handleEvents(receiveCancel: {
isCancelled = true
})
}
}
and somewhere in the app you do this to cancel the event
loadWidgets().cancel()
Also check this article
Thanks to ccwasden for the inspiration. This replicates Observable.create semantics with a pure Combine implementation without any superfluous entities.
AnyPublisher.create() Swift 5.6 Extension
public extension AnyPublisher {
static func create<Output, Failure>(_ subscribe: #escaping (AnySubscriber<Output, Failure>) -> AnyCancellable) -> AnyPublisher<Output, Failure> {
let passthroughSubject = PassthroughSubject<Output, Failure>()
var cancellable: AnyCancellable?
return passthroughSubject
.handleEvents(receiveSubscription: { subscription in
let subscriber = AnySubscriber<Output, Failure> { subscription in
} receiveValue: { input in
passthroughSubject.send(input)
return .unlimited
} receiveCompletion: { completion in
passthroughSubject.send(completion: completion)
}
cancellable = subscribe(subscriber)
}, receiveCompletion: { completion in
}, receiveCancel: {
cancellable?.cancel()
})
.eraseToAnyPublisher()
}
}
Usage
func doSomething() -> AnyPublisher<Int, Error> {
return AnyPublisher<Int, Error>.create { subscriber in
// Imperative implementation of doing something can call subscriber as follows
_ = subscriber.receive(1)
subscriber.receive(completion: .finished)
// subscriber.receive(completion: .failure(myError))
return AnyCancellable {
// Imperative cancellation implementation
}
}
}