Search code examples
iosswiftnsuserdefaultsuserdefaults

Sometimes, UserDefaults reloads the deleted data after relaunching the app


I found a weird behavior when using UserDefaults to save/load data, what I do as below.

  1. Swipe to delete data/row in tableView.
  2. Relaunch the app to load data from UserDefaults again.
  3. I try this many times and using both simulator and iPhone, sometimes the removed row is showed again when app is re-launched.

Well, it is a random issue that I check my code but didn't found clues. My data model is very simple, so it should be saved and load quickly. Would you gives me some hint/advice?

Model

class Note: Codable {
    var content: String
    
    init(content: String) {
        self.content = content
    }
}

ViewController

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    ...
    var notes = [Note]()

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        loadData()
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return notes.count
    }
    
   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.accessoryType = .disclosureIndicator
        
        let node = notes[indexPath.row].content
        cell.textLabel?.text = node
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let vc = DetailViewController()
        vc.bodyText = notes[indexPath.row].content
        
        navigationController?.pushViewController(vc, animated: true)
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            notes.remove(at: indexPath.row)
            print("notes: \(notes)")
            saveData()
            tableView.deleteRows(at: [indexPath], with: .fade)
            count = notes.count
        }
    }
    
    @objc func createNewNote() {
        let vc = NewNoteViewController()
        vc.notes = notes
        navigationController?.pushViewController(vc, animated: true)
    }
    
    func loadData() {
        DispatchQueue.global().async { [weak self] in
            let defaults = UserDefaults.standard

            if let savedNotes = defaults.object(forKey: "notes") as? Data {
                let jsonDecoder = JSONDecoder()
                do {
                    self?.notes = try jsonDecoder.decode([Note].self, from: savedNotes)
                    print("Load notes: \(String(describing: self?.notes))")
                    DispatchQueue.main.async {
                        self?.tableView.reloadData()
                        self?.count = self?.notes.count ?? 0
                    }
                } catch {
                    print("Failed to load notes")
                }
            }
        }
    }
    
    func saveData() {
        DispatchQueue.global().async { [weak self] in
            let jsonEncoder = JSONEncoder()
            
            if let savedData = try? jsonEncoder.encode(self?.notes) {
                let defaults = UserDefaults.standard
                defaults.set(savedData, forKey: "notes")
                print("value saved")
            } else {
                print("Failed to save notes")
            }
        }
    }
}

Solution

  • UserDefaults doesn't write the data to disk immediately, it waits to perform batch writes for performance reasons.

    If you're re-launching the app through Xcode it might be killing the app before it has the chance to persist the changes.

    Try force closing the app on the device instead of re-launching with Xcode, or just wait a few more seconds before re-launching and this shouldn't happen.

    To be clear, what you're seeing is not an issue, it's a side-effect of the way you're launching the app. Don't worry about it, it won't affect the way your app works once it's installed.