Proper way to dispose of a disposable within an observable - swift

I have an HTTPService which returns an Observable<NSData>. My goal is to compose that service into another service, ServiceA which transforms that data for my use case. Using Observable.create in RxSwift 2.0.0-rc.0 in ServiceA it's straight forward enough. My question is how to properly handle the disposable returned from the subscription of the HTTPService.
If I don't do anything I get the compile time warning that the result of call is unused: http://git.io/rxs.ud. I understand from reading that if I do nothing it's likely ok: (where xs mentioned below is let xs: Observable<E> ....
In case xs terminates in a predictable way with Completed or Error message, not handling subscription Disposable won't leak any resources, but it's still preferred way because in that way element computation is terminated at predictable moment.
So here is how I am currently addressing it, and also where I am wondering if I am doing this properly or if I have misunderstood something.
public struct ServiceA{
public static func changes() -> Observable<ChangeSet>{
return Observable.create{ observable in
// return's Observable<NSData>
let request = HTTPService.get("https://httpbin.org/get")
let disposable = request.subscribe(
onNext: { data in
// Do more work to transform this data
// into something meaningful for the application.
// For example purposes just use an empty object
observable.onNext(ChangeSet())
observable.onCompleted()
},
onError:{ error in
observable.onError(error)
})
// Is this the right way to deal with the
// disposable from the subscription in this situation?
return AnonymousDisposable{
disposable.dispose()
}
}
}
}

As documentation says
subscribe function returns a subscription Disposable that can be used to cancel computation and free resources.
Preferred way of terminating these fluent calls is by using
.addDisposableTo(disposeBag) or in some equivalent way.
When disposeBag gets deallocated, subscription will be automatically
disposed.
Actually your example looks fine in terms of rules, but it loos pretty bad ;) (Also it would be ok, if you would just return this disposable) :
public static func changes() -> Observable<ChangeSet>{
return Observable.create{ observable in
// return's Observable<NSData>
let request = HTTPService.get("https://httpbin.org/get")
return request.subscribe(
onNext: { data in
// Do more work to transform this data
// into something meaningful for the application.
// For example purposes just use an empty object
observable.onNext(ChangeSet())
observable.onCompleted()
},
onError:{ error in
observable.onError(error)
})
}
But as you you returning Observeble I wonder, why you dont just use map operator ?
In your example it would be something like this:
public static func changes() -> Observable<ChangeSet> {
return HTTPService.get("https://httpbin.org/get")
.map(ChangeSet.init)
}

Related

What is the Swift concurrency equivalent to a promise–resolver pair?

With the PromiseKit library, it’s possible to create a promise and a resolver function together and store them on an instance of a class:
class ExampleClass {
// Promise and resolver for the top news headline, as obtained from
// some web service.
private let (headlinePromise, headlineSeal) = Promise<String>.pending()
}
Like any promise, we can chain off of headlinePromise to do some work once the value is available:
headlinePromise.get { headline in
updateUI(headline: headline)
}
// Some other stuff here
Since the promise has not been resolved yet, the contents of the get closure will be enqueued somewhere and control will immediately move to the “some other stuff here” section; updateUI will not be called unless and until the promise is resolved.
To resolve the promise, an instance method can call headlineSeal:
makeNetworkRequest("https://news.example/headline").get { headline in
headlineSeal.fulfill(headline)
}
The promise is now resolved, and any promise chains that had been waiting for headlinePromise will continue. For the rest of the life of this ExampleClass instance, any promise chain starting like
headlinePromise.get { headline in
// ...
}
will immediately begin executing. (“Immediately” might mean “right now, synchronously,” or it might mean “on the next run of the event loop”; the distinction isn’t important for me here.) Since promises can only be resolved once, any future calls to headlineSeal.fulfill(_:) or headlineSeal.reject(_:) will be no-ops.
Question
How can this pattern be translated idiomatically into Swift concurrency (“async/await”)? It’s not important that there be an object called a “promise” and a function called a “resolver”; what I’m looking for is a setup that has the following properties:
It’s possible for some code to declare a dependency on some bit of asynchronously-available state, and yield until that state is available.
It’s possible for that state to be “fulfilled” from potentially any instance method.
Once the state is available, any future chains of code that depend on that state are able to run right away.
Once the state is available, its value is immutable; the state cannot become unavailable again, nor can its value be changed.
I think that some of these can be accomplished by storing an instance variable
private let headlineTask: Task<String, Error>
and then waiting for the value with
let headline = try await headlineTask.value
but I’m not sure how that Task should be initialized or how it should be “fulfilled.”
Here is a way to reproduce a Promise which can be awaited by multiple consumers and fulfilled by any synchronous code:
public final class Promise<Success: Sendable>: Sendable {
typealias Waiter = CheckedContinuation<Success, Never>
struct State {
var waiters = [Waiter]()
var result: Success? = nil
}
private let state = ManagedCriticalState(State())
public init(_ elementType: Success.Type = Success.self) { }
#discardableResult
public func fulfill(with value: Success) -> Bool {
return state.withCriticalRegion { state in
if state.result == nil {
state.result = value
for waiters in state.waiters {
waiters.resume(returning: value)
}
state.waiters.removeAll()
return false
}
return true
}
}
public var value: Success {
get async {
await withCheckedContinuation { continuation in
state.withCriticalRegion { state in
if let result = state.result {
continuation.resume(returning: result)
} else {
state.waiters.append(continuation)
}
}
}
}
}
}
extension Promise where Success == Void {
func fulfill() -> Bool {
return fulfill(with: ())
}
}
The ManagedCriticalState type can be found in this file from the SwiftAsyncAlgorithms package.
I think I got the implementation safe and correct but if someone finds an error I'll update the answer. For reference I got inspired by AsyncChannel and this blog post.
You can use it like this:
#main
enum App {
static func main() async throws {
let promise = Promise(String.self)
// Delayed fulfilling.
let fulfiller = Task.detached {
print("Starting to wait...")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("Promise fulfilled")
promise.fulfill(with: "Done!")
}
let consumer = Task.detached {
await (print("Promise resolved to '\(promise.value)'"))
}
// Launch concurrent consumer and producer
// and wait for them to complete.
try await fulfiller.value
await consumer.value
// A promise can be fulfilled only once and
// subsequent calls to `.value` immediatly return
// with the previously resolved value.
promise.fulfill(with: "Ooops")
await (print("Promise still resolved to '\(promise.value)'"))
}
}
Short explanation
In Swift Concurrency, the high-level Task type resembles a Future/Promise (it can be awaited and suspends execution until resolved) but the actual resolution cannot be controlled from the outside: one must compose built-in lower-level asynchronous functions such as URLSession.data() or Task.sleep().
However, Swift Concurrency provides a (Checked|Unsafe)Continuation type which basically act as a Promise resolver. It is a low-lever building block which purpose is to migrate regular asynchronous code (callback-based for instance) to the Swift Concurrency world.
In the above code, continuations are created by the consumers (via the .value property) and stored in the Promise. Later, when the result is available the stored continuations are fulfilled (with .resume()), which resumes the execution of the consumers. The result is also cached so that if it is already available when .value is called it is directly returned to the called.
When a Promise is fulfilled multiple times, the current behavior is to ignore subsequent calls and to return aa boolean value indicating if the Promise was already fulfilled. Other API's could be used (a trap, throwing an error, etc.).
The internal mutable state of the Promise must be protected from concurrent accesses since multiple concurrency domains could try to read and write from it at the same time. This is achieve with regular locking (I believe this could have been achieved with an actor, though).

In a Combine Publisher chain, how to keep inner objects alive until cancel or complete?

I've created a Combine publisher chain that looks something like this:
let pub = getSomeAsyncData()
.mapError { ... }
.map { ... }
...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
return wsi.subject
}
.share().eraseToAnyPublisher()
It's a flow of different possible network requests and data transformations. The calling code wants to subscribe to pub to find out when the whole asynchronous process has succeeded or failed.
I'm confused about the design of the flatMap step with the WebSocketInteraction. That's a helper class that I wrote. I don't think its internal details are important, but its purpose is to provide its subject property (a PassthroughSubject) as the next Publisher in the chain. Internally the WebSocketInteraction uses URLSessionWebSocketTask, talks to a server, and publishes to the subject. I like flatMap, but how do you keep this piece alive for the lifetime of the Publisher chain?
If I store it in the outer object (no problem), then I need to clean it up. I could do that when the subject completes, but if the caller cancels the entire publisher chain then I won't receive a completion event. Do I need to use Publisher.handleEvents and listen for cancellation as well? This seems a bit ugly. But maybe there is no other way...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
self.currentWsi = wsi // store in containing object to keep it alive.
wsi.subject.sink(receiveCompletion: { self.currentWsi = nil })
wsi.subject.handleEvents(receiveCancel: {
wsi.closeWebSocket()
self.currentWsi = nil
})
Anyone have any good "design patterns" here?
One design I've considered is making my own Publisher. For example, instead of having WebSocketInteraction vend a PassthroughSubject, it could conform to Publisher. I may end up going this way, but making a custom Combine Publisher is more work, and the documentation steers people toward using a subject instead. To make a custom Publisher you have to implement some of things that the PassthroughSubject does for you, like respond to demand and cancellation, and keep state to ensure you complete at most once and don't send events after that.
[Edit: to clarify that WebSocketInteraction is my own class.]
It's not exactly clear what problems you are facing with keeping an inner object alive. The object should be alive so long as something has a strong reference to it.
It's either an external object that will start some async process, or an internal closure that keeps a strong reference to self via self.subject.send(...).
class WebSocketInteraction {
private let subject = PassthroughSubject<String, Error>()
private var isCancelled: Bool = false
init() {
// start some async work
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if !isCancelled { self.subject.send("Done") } // <-- ref
}
}
// return a publisher that can cancel the operation when
var pub: AnyPublisher<String, Error> {
subject
.handleEvents(receiveCancel: {
print("cancel handler")
self.isCancelled = true // <-- ref
})
.eraseToAnyPublisher()
}
}
You should be able to use it as you wanted with flatMap, since the pub property returned publisher, and the inner closure hold a reference to self
let pub = getSomeAsyncData()
...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
return wsi.pub
}

Combine: How to clean up resources while an AnyCancellable is being cancelled?

Overview:
I have a async task to fetch from the database
I have created a Future for the async task (fetching from the database).
Question:
How can execute custom code when the Future is cancelled?
Purpose:
I would like the database connection to be closed when the subscription is cancelled.
For example, I would like to use Combine to rewrite this helper method:
// Similar to https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640564-performbackgroundtask
func withDatabaseFTSContext(block: #escaping (FMDatabase?) -> Void) {
queue.async {
guard let database = self.database else {
block(nil)
return
}
database.open()
let simpleTokenizer = FMSimpleTokenizer(locale: nil)
FMDatabase.registerTokenizer(simpleTokenizer, withKey: "simple")
database.installTokenizerModule()
block(database)
database.close()
}
}
Could I leverage Combine to rewrite this method to return FMDatabase as a parameter of a publisher?
I was attempting to use Combine but it does not work. The database will be closed before cancel()
private func withDatabaseFTSContext() -> AnyPublisher<FMDatabase?, Never> {
return Future<FMDatabase?, Never> { promise in
self.queue.async {
guard let database = self.database else {
promise(.success(nil))
return
}
database.open()
let simpleTokenizer = FMSimpleTokenizer(locale: nil)
FMDatabase.registerTokenizer(simpleTokenizer, withKey: "simple")
database.installTokenizerModule()
promise(.success(database))
database.close() // When to close this database? Currently it will be closed before `cancel()`
}
}.eraseToAnyPublisher()
}
Short answer: there isn't a callback that triggers through to the underlying Future that you can use to clean things up on a subscriber cancel. In the Combine design, these functions are very intentionally separated and don't have reference links back to their publishers.
(In addition, Future is a tricky figure in the Combine world because the closure is invoked immediately upon creation time, rather than when you have a subscription (if you want that, wrap in the Future publisher in a Deferred publisher)).
All that being said, what you likely want to do to solve your underlying problem is reframe how you're treating this to separate the concerns of managing the FMDB instance and publishing data. One pattern that's been reasonably useful in this context is to the make an object that holds the lifetime of the FMDB reference, and handle cleaning up resources on it's deinit(). You can then also have a function which vends a Publisher of whatever you need from that same object, and then the cancellation of the request is changed semantically to only cancelling getting the database, not cancelling and cleaning up the database connection.

Race condition in unit tests

I'm currently testing a number of classes that do network stuff like REST API calls, and a Realm database is mutated in the process. When I run all the different tests I have at once, race conditions appear (but of course, when I run them one by one, they all pass). How can I reliably make the tests pass?
I have tried to call the mentioned functions in a GCD block like this:
DispatchQueue.main.async {
self.function.start()
}
One of my tests are still failing, so I guess the above didn't work. I have enabled Thread Sanitizer and it reports, from time to time, that race conditions appear.
I can't post code, so I'm looking for conceptual solutions.
Typically some form of dependency injection. Be it an internally exposed var to the DispatchQueue, a default argument in a function with the queue, or a constructor argument. You just need some way to pass a test queue that dispatches the event when you need to.
DispatchQueue.main.async will schedule the block async to the callee on the main queue and therefore isn't guarenteed by the time you make an assertion.
Example (disclaimer: I'm typing from memory so it might not compile but it gives the idea):
// In test code.
struct TestQueue: DispatchQueue {
// make sure to impement other necessary protocol methods
func async(block: () -> Void) {
// you can even have some different behavior for when to execute the block.
// also you can pass XCTestExpectations to this TestQueue to be fulfilled if necessary.
block()
}
}
// In source code. In test, pass the Test Queue to the first argument
func doSomething(queue: DispatchQueue = DispatchQueue.main, completion: () -> Void) {
queue.async(block: completion)
}
Other methods of testing async and eliminating race conditions revolve around craftily fulfilling an XCTestExpectation.
If you have access to the completion block that is eventually invoked:
// In source
class Subject {
func doSomethingAsync(completion: () -> Void) {
...
}
}
// In test
func testDoSomethingAsync() {
let subject = Subject()
let expect = expectation(description: "does something asnyc")
subject.doSomethingAsync {
expect.fulfill()
}
wait(for: [expect], timeout: 1.0)
// assert something here
// or the wait may be good enough as it will fail if not fulfilled
}
If you don't have access to the completion block it usually means finding a way to inject or subclass a test double that you can set an XCTestExpectation on and will eventually fulfill the expectation when the async work has completed.

how to translate an if-else in RxSwift?

I'm trying to learn the library RxSwift
I have some code like this:
if data.checkAllIsOk()
{
[do things]
}
else
{
[show alert]
}
Now i need to update the data from the server before checking, so i have modeled a getData() that return an Observable.
My current approach is this:
getData()
>- flatMap{ (data:Data) -> Observable<Bool> in
_=0 // workaround for type inference bugs
return just(data.checkAllIsOk())
}
>- subscribeNext{ (ok) -> Void in
if ok
{
[do the things]
}
else
{
[show the alert]
}
}
>- disposeBag.addDisposable()
It works (or it should, i'm still writing it), but it feels wrong.. is there a more "reactive" way to do it?
What are the most appropriate operators to use?
Maybe returning an error for “false” and use the catch block?
Update
Following the approach suggested by ssrobbi i splitted the 2 branches in 2 different subscribeNext, and used filter to select the positive or negative branch. This is the code resulting:
let checkData=getData()
>- flatMap{ (data:Data) -> Observable<Bool> in
_=0
return just(data.checkAllIsOk())
}
>- shareReplay(1)
}
[...]
checkData
>- filter{ (ok) -> Bool in
ok == true
}
>- subscribeNext{ (_) -> Void in
[do the things]
}
>- disposeBag.addDisposable()
checkData
>- filter{ (ok) -> Bool in
ok == false
}
>- subscribeNext{ (_) -> Void in
[show the alert]
}
>- disposeBag.addDisposable()
The advantage of this approach is that i can reuse only one of the two branches in other parts of the code, without rewriting the subscribe body (less duplication is always good!)
Update
After some discussions in the RxSwift slack i added the shareReplay(1), so the getData() isn't repeated.
So to be honest I'm still learning as well, and I don't have RxSwift in front of me right now (so someone correct me if I'm spewing BS), but maybe I can give lead you into the right direction.
Your solution does work, but as you said it isn't very "reactive". I think the problem is that you have your data flow set up in a way that it has to make an imperative decision on whether to show an alert or do stuff. What should happen, is that instead of getData returning an observable, getData should get whatever data it needs to (whether it's from a network, core data, etc), and then it should update an observable property.
For the do things:
Now, you would observe that property, map it to check if it's okay, and subscribe to it like you did, check if it's true, and if it is do things. (and add disposable)
For the alert:
You'd do the exact same thing, observing that same property again, but check for the opposite case and do stuff.
I think what's not super reactive about it is that you're synchronously waiting on a response from that getData() function, which creates a scenario where you now have state, whether to show an alert, or do that extra work. They're not derived from the value stream of some other property. Showing the alert and doing things are only related to each other because you set up your code imperatively.
EDIT: Instead of checking with an if statement whether or not that's true, perhaps you could put it through a filter before subscribing instead.
I don't know RXSwift, but I do know functional programming (RXSwift is functional as well). An if else statement is already on its lowest form and you don't need to do functional branching, which would work but it makes it less readable.
You could change your if else to condition ? a : b if you want to be more functional (Haskell's if else is exactly that). But that makes it less readable as well, I would just stick to what you got ;)
getData() should return an Observable<Data> and the contained data should already be ok. In other words, if getData() is implemented correctly, then calling data.checkAllIsOk() on the data pushed out of the observable should always return true.
So what you should have outside of getData() is something like (in Rx v2 with Swift v2):
getData().subscribe { event in
switch event {
case .Next(let data):
[do things with data]
case .Error(let error):
[show the alert]
}
The problem with your updated approach is that checkData is an Observable<Bool>, so you can't really "do stuff" with it.
I think what you want is this (decomposed for clarity):
func isDataOk(_ data: Data) -> Bool { /* ... */ }
let data = getData().shareReplay(1)
let goodData = data.filter(isDataOk)
let badData = data.filter{ isDataOk($0) == false }
goodData
.subscribe( /* do stuff */ )
.addDisposableTo(disposeBag)
badData
.subscribe( /* show alert */ )
.addDisposableTo(disposeBag)
But I agree with Daniel in that this is a great opportunity to use an observable that errors. Something like this:
func passOrThrow(_ data: Data) throws -> Data { /* throw an error here if data is bad */ }
getData()
.map(passOrThrow)
.subscribe(onNext: { data in
// do the things
}, onError: { error in
// show the alert
}).addDisposableTo(disposeBag)