Why does this assignment happen even when the values are equal?

We had this thread 8 months ago, and here's a suggested solution.

Generalising:

protocol AssignIfDifferent {}
protocol AssignIfDifferentRef: AnyObject {}

extension AssignIfDifferent {
    mutating func assignIfDifferent<T: Equatable>(_ path: WritableKeyPath<Self, T>, _ newValue: T) {
        if self[keyPath: path] != newValue {
            self[keyPath: path] = newValue
        }
    }
}

extension AssignIfDifferentRef {
    func assignIfDifferent<T: Equatable>(_ path: ReferenceWritableKeyPath<Self, T>, _ newValue: T) {
        if self[keyPath: path] != newValue {
            self[keyPath: path] = newValue
        }
    }
}

struct Reminder: AssignIfDifferent {
    var fireDate: Date = Date.distantPast {
        didSet {
            print("CHANGE!")
        }
    }
}

class ReminderRef: AssignIfDifferentRef {
    var fireDate: Date = Date.distantPast {
        didSet {
            print("CHANGE!")
        }
    }
}

var reminder = Reminder()
let sameDate = reminder.fireDate
reminder.assignIfDifferent(\.fireDate, sameDate) // no change
reminder.assignIfDifferent(\.fireDate, sameDate + 1) // change

let reminderRef = ReminderRef()
let oldDate = reminderRef.fireDate
reminderRef.assignIfDifferent(\.fireDate, oldDate) // no change
reminderRef.assignIfDifferent(\.fireDate, oldDate + 1) // change

With subscripts:

extension AssignIfDifferent {
    subscript<T: Equatable>(path: KeyPath<Self, T>) -> T {
        self[keyPath: path]
    }
    subscript<T: Equatable>(path: WritableKeyPath<Self, T>) -> T {
        get { self[keyPath: path] }
        set {
            if self[keyPath: path] != newValue {
                self[keyPath: path] = newValue
            }
        }
    }
}

extension AssignIfDifferentRef {
    subscript<T: Equatable>(path: KeyPath<Self, T>) -> T {
        self[keyPath: path]
    }
    subscript<T: Equatable>(path: ReferenceWritableKeyPath<Self, T>) -> T {
        get { self[keyPath: path] }
        set {
            if self[keyPath: path] != newValue {
                self[keyPath: path] = newValue
            }
        }
    }
}

The use site becomes:

reminder[\.fireDate] = sameDate // no change
3 Likes