Search code examples
iosswiftcombine

How to stop storing AnyCancellable after Swift Combine Sink has received at least one value?


I have a sink that needs to be canceled as soon as I receive the first value. I don't care about future values, just the first one published. Because without storing the AnyCancelable created by the sink, the garbage collector will delete the sink, I must store it. At the same time, I must also clean it up after the sink has completed, otherwise, I will have a memory leak.

I built one using a UUID → AnyCancelable map, but I am worried that this is more complex than it should be; is there another way of doing this? What's recommended by Combine?

@Published var locationState: (location: CLLocation?, error: Error?)?
var requestLocationSinks: [String: AnyCancellable] = [:]

// 1. Generate ID to uniquely identify the current sink.
let sinkID = UUID().uuidString

// 2. Start the sink and store it in our ID → AnyCancellable dictionary.
requestLocationSinks[sinkID] = $locationState.sink { locationState in
    if let locationState = locationState {
        invokeCallbackWithLocationState(locationState)
    }
    // 3. Remove the stored AnyCancellable as soon as we received our first value!
    self.requestLocationSinks.removeValue(forKey: sinkID)
}

Solution

  • If you just need to keep it alive until the sink is called once, you can just create a temporary variable

    var cancellable: AnyCancellable?
    cancellable = $locationState.sink { locationState in
        if let locationState = locationState {
            invokeCallbackWithLocationState(locationState)
        }
        cancellable = nil
    }
    

    This will retain the AnyCancellable long enough (because the closure retains the reference)