CristianMoisei
CristianMoisei

Reputation: 2279

Swift detect in the setter which object in an array was changed

I have an array of Habit objects which I am modifying in memory and a getter/setter to read and write it to documents storage as JSON whenever changes are made. I have split each object into its own JSON file and so far everything works, however changing one object in the array will re-write all the files. I understand this is what I instructed my setter to do, but is there a way to only write the objects in newValue that were changed?

extension Storage {
    static var habits: [Habit] {
        get { ... }
        set {
            newValue.forEach({
                let data = try! JSONEncoder().encode($0)
                do { try data.write(to: Storage.habitsFolder.appendingPathComponent("habit-\($0.id).json")) }
                catch let error {
                    print("Failed to write Habit \($0.id): \(error.localizedDescription)")
                }
            })
        }
    }
}

The way I would make a change now is Storage.habits[0].name = "Some name". Which calls the setter, which then re-writes the files for each habit. So I was wondering if there's some way to detect which part of newValue changed or pass an index to it and only update that file.

Is the only way to go about this to have the array be get-only and use a different method for setting each habit file?

Thank you, and apologies if this is a silly question.

Update: adding Habit class for more context

class Habit: Codable, CustomStringConvertible {
    // MARK: - Properties
    var id: Int
    var name: String?
    var notes: String?
    var icon: String
    var repeatPattern: RepeatPattern
    var entries: [HabitEntry]
    var reminders: [Reminder]
    init(id: Int, name: String?, notes: String?, icon: String, entries: [HabitEntry], reminders: [Reminder]) {
        self.id = id
        self.name = name
        self.notes = notes
        self.icon = icon
        self.repeatPattern = RepeatPattern(pattern: 0, startDay: calendar.today, enabledWeekdays: [1,2,3,4,5,6,7], expectedTimes: 1)
        self.entries = entries
        self.reminders = reminders
    }
    var description: String { return "Habit id: \(id), named: \(name ?? "nil"), \nnotes: \(notes ?? "nil"), \nicon: \(icon), \nrepeatPattern: \(repeatPattern), \nentries: \(entries), \nreminders: \(String(describing: reminders))" }
}

Upvotes: 2

Views: 381

Answers (1)

aheze
aheze

Reputation: 30336

You could make a property to store the existing habits first

class Storage {
    static var existingHabits = [Habit]()
}

Then, inside the set, see which Habits are new:

static var habits: [Habit] {
    get { ... }
    set {

        var habitsToChange = [Habit]()

        if existingHabits.count == 0 { /// it's empty, just write with newValue
            habitsToChange = newValue
        } else {
            let differentIndicies = zip(existingHabits, newValue).enumerated().filter() {
                $1.0 != $1.1
            }.map{$0.0} /// from https://stackoverflow.com/a/30685226/14351818
        
            for index in differentIndicies {
                habitsToChange.append(newValue[index])
            }
        }

        habitsToChange.forEach({ /// loop over the habits that have changed
            do {
                try JSONEncoder().encode($0).write(to: habitsFolder.appendingPathComponent("habit-\($0.id).json"))
            } catch {
                print("Failed to write Habit \($0.id): \(error)")
            }
        })

        existingHabits = newValue /// set existing to the new value
    }
}

Upvotes: 1

Related Questions