Search code examples
swiftuitableviewnsuserdefaultsswift4.2

Saving to UserDefaults from UITableViewCell causes crash Swift 4.2


A UISwitch is placed within a UITableViewCell, when switch is toggled want to save the state of the switch to UserDefaults.

class SettingsTableViewCell: UITableViewCell {
   @objc fileprivate func switchToggled(_ cellSwitch: UISwitch) {
        if let option = self.option {
            switch option.tag {
            case 1:
                let state = cellSwitch.isOn
                SettingsValues.standard.setState(state)
            default:
                print(">>> Settings Cell >> Tag for option does not exist")
            }
        }
    }
 }

On viewDidLoad it works fine and it saves to UserDefaults, however the moment you scroll the cell off screen, its dequeued. When you scroll back and flip the switch it cause a crash:

EXC_BAD_ACCESS(code=1, address=0x20)

The SettingsValues is a custom singleton, the function saves to UserDefaults. I have tried moving the saving out of UITableViewCell through a protocol to the ViewController but it still causes a crash when saving to UserDefaults.

I feel it has something to do with the dequeue nature of the TableViewCell but I don't understand what is happening hence not sure how to tackle this problem.

EDIT: The singleton

final class SettingsValues: NSObject {
   static let standard = SettingsValues()

   private override init() { super.init() }

   @objc dynamic var state: Bool = UserDefaults.standard.bool(forKey: kEnableState) {
       didSet {
           saveSettingsState()
       }
   }

   private func saveSettingsState() {
        UserDefaults.standard.set(state, forKey: kEnableState)
   }

   func setState(_ bool: Bool) {
        self.state = bool
   }
}

The crash is on line UserDefaults.standard.set(state, forKey: kEnableState). I set a breakpoint on that line and when I press step over, the EXC_BAD_ACCESS occurs


Solution

  • I found the bug. It was because of the observers. There was another cell in the tableView that had an observer for the state. I did not remove the observer on deinithence multiple instances of the observer was added everytime the cell got dequeued and redrawn.

    Lesson to all watch how many times your observers are being created for the same reference object. I won't go so far to always remove on deinit sometimes deinit won't ever be called. Example: a viewcontroller on the tabbarcontroller. Safer bet would be to watch the number of created instances. In this case I should have removed the observer on deinit

    Thanks to all who left a comment and helped jiggle things up