Reputation: 9700
Let's say I have the following set up:
struct Sandwich {
var bread: Bread
var fillings: [Filling]
var slices: Int
}
enum Bread: String {
case white
case wholemeal
}
struct Filling {
let name: String
let calories: Int
}
So, we have a parent struct which has some other properties, enums, other structs, etc. inside as children. They may have their own children too.
What I'm trying to do is detect any change to my Sandwich
, and post a notification or use Combine to publish a change, etc.
So, here's my sandwich:
var sandwich = Sandwich(bread: .white, fillings: [Filling(name: "Chicken", calories: 100)], slices: 2)
Now, let's say I come along a few minutes later and decide to add another filling:
sandwich.fillings.append(Filling(name: "Lettuce", calories: 10)]
Or, perhaps they decide to increase their order:
sandwich.slices += 2
What I'm trying to do, is stay in the loop. I want a notification, something, to let me know if my Sandwich
changed in any way, whether one of its child properties changed, or one of its grandchild's properties, or even further down the tree.
I'd also like to use these changed versions of my Sandwich
to build an undo/redo queue, so the state at each change would ideally be loggable / storable.
I realise I could write a load of delegates and stuff to do this, but I want something that will automatically work and keep working if the model changes (i.e. if I add a new property, etc.)
Upvotes: 1
Views: 1094
Reputation: 4037
Both KVO
and Combine
works for class inherited from NSObject
.
the automatically change needs runtime
.
class
, instead of struct
class Sandwich: NSObject {
var bread = Bread.white
@objc dynamic var fillings = [Filling]()
@objc dynamic var slices: Int = 0
}
enum Bread: String {
case white
case wholemeal
}
class Filling: NSObject {
@objc dynamic var name: String = ""
@objc dynamic var calories: Int = 0
}
class ViewController: UIViewController {
@objc var food = Sandwich()
var observationFilling: NSKeyValueObservation?
var observationSlice: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
// use KVO
observationFilling = observe(\.food.fillings, options: [.new]) { object, change in
if let val = change.newValue{
print ("food.fillings now \(val).")
}
}
observationSlice = observe(\.food.slices, options: [.new]) { object, change in
if let val = change.newValue{
print ("food.slices now \(val).")
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
food.fillings.append(Filling())
food.slices += 1
}
}
Use Combine:
class ViewController: UIViewController {
@objc var food = Sandwich()
var cancellableFilling: Cancellable?
var cancellableSlice: Cancellable?
override func viewDidLoad() {
super.viewDidLoad()
// use Combine
cancellableFilling = food.publisher(for: \.fillings)
.sink(){
val in
print("food.fillings now \(val).")
}
cancellableSlice = food.publisher(for: \.slices)
.sink(){
val in
print("food.slices now \(val).")
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
food.fillings.append(Filling())
food.slices += 1
}
}
Upvotes: 1