3

I've been using ReactiveSwift for a few months now but there is a something that I dont fully understand: lifetime objects.

For example, lets say I have a SignalProducer which will make an API call, this is wrapped on a class:

class ServiceWrapped {

    private let service: Service // the method called on this object returns the SignalProducer
    private let (lifetime, token) = Lifetime.make()

    // more stuff

    func fetchSomething(completion: @escaping (Value?, Error?) -> Void) {
        lifetime += service.fetchSomething()
            .startWithResult { result in
                switch result {
                case .success(let value):
                    completion(value, nil)
                case .failure(let error):
                    completion(nil, error)
                }
        }
    }
}

My question is: Is it necessary to use lifetime on this case?

I understood that lifetime will retain the service call so it has something when it return but since this is also wrapped on ServiceWrapped I don't think using lifetime is really necessary.

Thanks in advance.

rgkobashi
  • 2,551
  • 19
  • 25
  • what's going to keep the subscription alive then? – Alexander Nov 28 '18 at 02:53
  • the closure on `startWithResult ` isn't? it will be executed when the API returns and after that the subscription is not alive which is fine. Even if I remove the lifetime I am still able to receive the response, that is why I am not sure what if it is needed here or not. – rgkobashi Nov 28 '18 at 06:56
  • also the instance of `ServiceWrapped ` will have a reference to `service` so it will keep alive the subscription. This is my understanding... – rgkobashi Nov 29 '18 at 05:27
  • The strong reference to `service` will keep `service` alive. But it won't keep alive whatever is returned by `fetchSomething()` and `startWithResult`. – Alexander Nov 29 '18 at 16:40
  • 2
    @alexander See [the ReactiveSwift documentation](https://github.com/ReactiveCocoa/ReactiveSwift/blob/510e65c895a898fed4029270601fd2073f839a2d/Documentation/APIContracts.md#a-signal-is-alive-as-long-as-it-is-publicly-reachable-or-is-being-observed) on signal lifetime: "a Signal retains itself as long as there is still an active observer." – jjoelson Nov 29 '18 at 19:34

1 Answers1

6

You're correct that you don't need to keep the result of startWithResult in order to keep the subscription alive. The relevant part of the documentation says:

A Signal must be publicly retained for attaching new observers, but not necessarily for keeping the stream of events alive. Moreover, a Signal retains itself as long as there is still an active observer.

So as long as you don't dispose the object returned from startWithResult, the operation will continue even if you don't retain it.

Rather, Lifetime is about cancelling operations. In this case, because you've attached the result of startWithResult to ServiceWrapped's lifetime, the operation will be cancelled when the ServiceWrapped object is deallocated. If you omit lifetime +=, then the operation will continue even if ServiceWrapped is deallocated.

A practical example of why this is useful is if you have a view controller that loads an image from the web. If the user dismisses the view controller before the image is finished loading then you probably want to cancel the web request. You can do that by tying the image load producer to the lifetime of the view controller. It's not about keeping the web request alive, it's about cancelling it when it's no longer necessary.

As an aside about style, the documentation recommends you use operators rather than handling the result of the startWithResult:

func fetchSomething(completion: @escaping (Value?, Error?) -> Void) {
    service.fetchSomething()
        .take(during: lifetime)
        .startWithResult { result in
            switch result {
            case .success(let value):
                completion(value, nil)
            case .failure(let error):
                completion(nil, error)
            }
    }
}
jjoelson
  • 5,771
  • 5
  • 31
  • 51
  • oh now it makes sense, thank you very much for the explanation and the example. Just one more thing, in this scenario using `.take(during: lifetime)` or `lifetime +=` will serve the same purpose right? – rgkobashi Nov 30 '18 at 00:30
  • 3
    Yes. I believe there's actually a subtle distinction in that disposing the `Disposable` returned from `startWithResult` sends an `interrupted` event whereas `take(during:)` sends a `completed` event, but that difference is unlikely to ever matter in practice. In this particular case, `startWithResult` only cares about `value` and `failed` events anyway. – jjoelson Nov 30 '18 at 14:59