Home > Back-end >  Swift Combine: sink() called after core data entity deletion
Swift Combine: sink() called after core data entity deletion

Time:03-30

I am using this code in a SwiftUI view model:

    car.publisher(for: \.sold, options: [.new])
        .removeDuplicates()
        .receive(on: RunLoop.main)
        .sink { [weak self] sold in
            guard let self = self else { return }
            .... here is reference the car entity for some logic ....
        }
        .store(in: &subscribers)

All works fine, until I actually delete that Car entity and the sink kicks in and then .... here is reference the car entity for some logic .... runs and it crashes trying to use a DELETED Core Data entity.

My pattern seems to be wrong here. Is there a way for that sink to cancel automatically when that car entity is deleted from context?

CodePudding user response:

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.

  • Related