Swift Combine Sink Stops Receiving Values After First Error

Sink does not receive values when connecting to finished a Publisher

PassthroughSubject receives a value and passes it along, it doesn't store the value so if you subscribe to it after the value passed through it, you won't receive it.

You can use CurrentValueSubject, which will store the latest value and resend it whenever somebody subscribes to it.

All of this is moot if you send completion: .finished though. A completed publisher won't send any values to a subscriber because it's completed.

This is your fixed code:

func somePublisher() -> AnyPublisher<Bool, Never> {
let subject = CurrentValueSubject<Bool, Never>(true)
return subject.eraseToAnyPublisher()
}

var bag: Set<AnyCancellable> = []

somePublisher()
.first()
.sink { _ in
print("Completed")
} receiveValue: {
print("Received \($0)")
}
.store(in: &bag)

Combine sink: ignore receiveValue, only completion is needed

CurrentValueSubject seems a confusing choice, because that will send an initial value (of Void) when you first subscribe to it.

You could make things less ambiguous by using Future, which will send one-and-only-one value, when it's done.

To get around having to receive values you don't care about, you can flip the situation round and use an output type of Result<Void, Error> and a failure type of Never. When processing your network request, you can then fulfil the promise with .failure(error) or .success(()), and deal with it in sink:

let pub = Future<Result<Void, Error>, Never> {
promise in
// Do something asynchronous
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
promise(.success(.success(())))
//or
//promise(.success(.failure(error)))
}
}.eraseToAnyPublisher()

// somewhere else...
pub.sink {
switch $0 {
case .failure(let error):
print("Whoops \(error)")
case .success:
print("Yay")
}
}

You're swapping ugly code at one end of the chain for ugly code at the other, but if that's hidden away behind AnyPublisher and you're concerned with correct usage, that seems the way to go. Consumers can see exactly what to expect from looking at the output and error types, and don't have to deal with them in separate closures.

Swift Combine: sink() called after core data entity deletion

You can use faultingState to track this scenario. From documentation:

0 if the object is fully initialized as a managed object and not transitioning to or from another state, otherwise some other value. This property allows you to determine if an object is in a transitional phase when receiving a key-value observing change notification.

So you can ignore this event like this:

.sink { [weak self] sold in
guard let self = self, car.faultingState == 0 else { return }
//
}

If you wanna actually cancel this sink, you can store cancellables inside the object so you can cancel them during prepareForDeletion.

To do this you need to change object code generation, more info can be found here. Change to Category/Extension - in this case you can create a class and override prepareForDeletion, and Xcode will still generate all the properties for you.

Now you can move all publisher logic into your class:

@objc(Car)
public class Car: NSManagedObject {
private var subscribers = Set<AnyCancellable>()

func observe<Value: Equatable>(keyPath: KeyPath<Item, Value>, receiveValue: @escaping ((Value) -> Void)) {
publisher(for: keyPath, options: [.new])
.removeDuplicates()
.receive(on: RunLoop.main)
.sink(receiveValue: receiveValue)
.store(in: &subscribers)
}

public override func prepareForDeletion() {
super.prepareForDeletion()
subscribers.removeAll()
}
}

Or just use it to store cancellables.

Future Combine sink does not recieve any values

Try to change/remove .cancel() method on your subscriptions. Seems you subscribe to the publisher, and then immediately cancel the subscription. The better option is to retain and store all your subscriptions in the cancellable set.

Combine sink does not complete when publisher fails

The issue you're seeing may be that a 404 isn't considered an error by underlying Cocoa libraries (URLSession specifically). This seems like something that's being done (or not) within the Squid library, and you're reacting to what it provides.

URLSession itself only throws errors when it's unable to get a response from the server - so when issues are related to not being able to resolve the hostname or make a connection kinds of things. If you do make a connection and get a response, URLSession won't throw an error, but the status code will be correctly reflected - it'll just "look" like a successful request.

The example above doesn't show that section, but what happens with a 404 response from URLSession is that the request completes, and the status code 404 is encoded within the response, but it's not thrown as an error state.

A typical pattern of dealing with this in a Combine publisher chain is by passing the result data from URLSession into a tryMap operator, where you can do further inspection of the result (status code, data, etc) and determine if you want to turn that into a thrown error (such as if you get a 404 response).

You can find an example of this kind of pattern at https://heckj.github.io/swiftui-notes/#patterns-datataskpublisher-trymap. The gist of this pattern is allowing you to define whatever error you want (assuming you a thrown exception in those cases) based on inspection of the result.

You can find other examples of using Combine (along with URLSession.dataTaskPublisher) in Using Combine.



Related Topics



Leave a reply



Submit