Remove from Array of Anycancellable When Publisher Finishes

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

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)

Advantage of AnyCancellable.store(in:) over SetAnyCancellable.insert(:)

If you know that you'll only need one reference to a AnyCancellable, then you can store a direct reference to that and avoid the Set<AnyCancellable>

Otherwise, there is no difference aside from the stylistic one. I prefer store(in:) to avoid the need to use a local variable to maintain readability. (Of course, you could wrap the return value of your pipeline in a Controller.cancellables.insert but that's just downright ugly IMO.)

I would still recommend using the store(in:) approach so you don't need to change your implementation if you do end up needing to hold onto more AnyCancellable references.

Swift Combine - Observe change of object's property publisher inside an array of objects

You are on the right track with needing a CombineLatestCollection for combining all isUploaded values of all cell view models.

My answer uses the combineLatest operator from the article you've linked.

Once you've combined all publishers, you simply need to call allSatisfy on the [Bool] to see if all cell view models finished uploading.

Now the only part missing is updating the subscription whenever the viewModels array changes - you can do this by observing the $viewModel publisher and passing in the updated array to a method, which combines the isUploaded properties of the updated view models.

class FileUploadScreenViewModel {
@Published var viewModels: [FileUploadCellViewModel] = []
@Published var isSendButtonEnabled: Bool = false

private var subscriptions = Set<AnyCancellable>()

init() {
// Whenever the viewModels array changes, set up the subscription on each element of the array
$viewModels.sink { [weak self] viewModels in
self?.bindIsSendButtonEnabled(viewModels: viewModels)
}.store(in: &subscriptions)
}

// Combine all isUploaded values from each element of viewModels and update isSendButtonEnabled accordingly
private func bindIsSendButtonEnabled(viewModels: [FileUploadCellViewModel]) {
let areUploaded = viewModels
.map(\.$isUploaded)
.combineLatest
.map { areUploaded in
areUploaded.allSatisfy { isUploaded in
isUploaded == true
}
}

let isDateCorrectPublisher = Just(true)

areUploaded
.combineLatest(isDateCorrectPublisher)
.map { $0 && $1 }
.assign(to: &$isSendButtonEnabled)
}
}

How do I cancel a combine subscription within a sink?

The problem in your code is that the subscription starts delivering values synchronously before the call to sink returns, and so before the call to store even begins.

One way to solve this is to turn aState into a ConnectablePublisher before subscribing. A ConnectablePublisher doesn't publish until its connect method is called. So call connect after store returns.

You can use the makeConnectable method on any Publisher whose Failure == Never to wrap it in a ConnectablePublisher.

let connectable = aState.makeConnectable()
connectable.sink { newState in
print("numberOfSubscriptions is: \(cancellableSet.count)")
switch newState {
case .loggedOut:
doSomethingElse()
case .doingSomething:
logUserOut()
}
}
.store(in: &cancellableSet)
connectable.connect()


Related Topics



Leave a reply



Submit