I'm seeing some struct vs class behavior that I don't really don't understand, when trying to assign a value using Combine.
Code:
import Foundation import Combine struct Passengers { var women = 0 var men = 0 } class Controller { @Published var passengers = Passengers() var cancellables = Set<AnyCancellable>() let minusButtonTapPublisher: AnyPublisher<Void, Never> init() { // Of course the real code has a real publisher for button taps :) minusButtonTapPublisher = Empty<Void, Never>().eraseToAnyPublisher() // Works fine: minusButtonTapPublisher .map { self.passengers.women - 1 } .sink { [weak self] value in self?.passengers.women = value }.store(in: &cancellables) // Doesn't work: minusButtonTapPublisher .map { self.passengers.women - 1 } .assign(to: \.women, on: passengers) .store(in: &cancellables) } } The error I get is Key path value type 'ReferenceWritableKeyPath<Passengers, Int>' cannot be converted to contextual type 'WritableKeyPath<Passengers, Int>'.
The version using sink instead of assign works fine, and when I turn Passengers into a class, the assign version also works fine. My question is: why does it only work with a class? The two versions (sink and assign) really do the same thing in the end, right? They both update the women property on passengers.
(When I do change Passengers to a class, then the sink version no longer works though.)
Structs are immutable, and are passedby value, notby reference. Therefore they can't bereference writable. When you changevarproperty instructentirestructis replaced (in the parent'svarproperty)..sinkversion work? That is mutating thewomenproperty just fine. After all that is avar(and so ispassengers). So if the sink version can do it, why not the assign version? I feel like I am missing a fundamental piece of understanding here.womenproperty entirepassengersvar of theControllerinstance gets recreated (struct mutation doesn't change it rather create new one with some data changed and rest copied), you're essentially setting new value to controller's property. That's allowed.ReferenceWritableKeyPathwould try to mutate justwomenproperty via reference. And you can't do anything via reference with structure.ReferenceWritableKeyPathandstructwon't work at all, no way.WritableKeyPath— maybe, probably, I haven't tried that.sinkworks because what's being captured isself, notself.passengers. Setting aside the concerns about the retain cycleassigncan cause if you doassign(to <anything>, on: self).store(in: &self.cancellables),sink { self.passengers.women = someNewValueworks likeassign(to: \passengers.women, on: self). It capturesself(a reference type, of typeController). contrast this withassign(to: \.women, on: self.passengers), which captures a value type (passesngers, of typePassengers).