I'm working on a method I can use to only update a property with a new value if the new value is different from the current. I have come up with a protocol for this. However, I am getting compiler errors:
Here are a few sample models:
class Model {
var id: Int = 0
}
class Person: Model {
var name: String = ""
}
class Dog: Model {
var color: UIColor = .black
}
Here is the protocol:
protocol Updatable: AnyObject{
@discardableResult
func update<Value: Equatable>(
_ keyPath: ReferenceWritableKeyPath<Self, Value>,
to value: Value
) -> Bool
}
And here is a default implementation:
extension Updatable {
@discardableResult
func update<Value: Equatable>(
_ keyPath: ReferenceWritableKeyPath<Self, Value>,
to value: Value
) -> Bool {
guard self[keyPath: keyPath] != value else { return false }
self[keyPath: keyPath] = value
return true
}
}
Now I would like for all of my Model subclasses to inherit this update feature:
extension Model: Updatable {}
However, I get a compiler error:
Protocol 'Updatable' requirement 'update(_:to:)' cannot be satisfied by a non-final class ('Model') because it uses 'Self' in a non-parameter, non-result type position
Now I understand why this would be a problem due to inheritance. The Self constraint would be different for each Model subclass and overriding would be impossible.
What gets me is that I can work around this issue by defining a blank protocol and adding a default protocol method with the same signature to an extension:
protocol Updatable2: AnyObject { }
extension Updatable2 {
@discardableResult
func update2<Value: Equatable>(
_ keyPath: ReferenceWritableKeyPath<Self, Value>,
to value: Value
) -> Bool {
print("Default: \(keyPath) -> \(value)")
guard self[keyPath: keyPath] != value else { return false }
self[keyPath: keyPath] = value
return true
}
}
extension Model: Updatable2 {}
The compiler gives me no trouble at all. All of the following are valid:
let model = Model()
model.update2(\.id, to: 200)
let person = Person()
person.update2(\.name, to: "Sally")
person.update2(\.id, to: 400)
let car = Car()
car.update2(\.wheels, to: 5)
let models: [Model] = [
model,
person,
car,
]
models.forEach { $0.update2(\.id, to: 666) }
How is this possible?