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
Why How to Make Same-Type Requirement in Swift with Generics? Is There Any Way
Shorthand for Wrapping a Swift Variable in an Optional
Member Operator '==' Must Have at Least One Argument of Type
Why This Line Is Not Covered? Xcode Code Coverage
Swift Switch Case Compiler Error
Swift: Rsa Encrypt a String with a Specific Private Key
Should I Keep Extensions in Their Own "Extensions" File
How to Invoke Method with Cvalistpointer Parameters in Swift
Convert Single File to Swift 3 in Xcode 8
Decrypted String Always Returning Null
Pass Type to Generic Function and Compare
How to Set First Responder for Nstextview in Swift
Different UIfont Sizes for Different iOS Devices in Swift
+' Is Deprecated: Mixed-Type Addition Is Deprecated in Swift 3.1