SafeFastExpressive
SafeFastExpressive

Reputation: 3805

Crash accessing Swift Dictionary Value in Singleton

I have a simple class I use to track usage of various app features in a dictionary. The dictionary is saved to NSUserDefaults when App is sent to background, and read from NSUserDefaults when App is first launched. After adding a few usage keys to the dictionary, sending to background, then re-opening and writing to it I get a bad access error.

The dictionary should be writable, I re-allocate a new dictionary when reading from NSUserDefaults and add the saved usage keys/objects to it. The key and object are both valid ([String:String] for key, objects) when I'm writing, as are my class object and the dictionary itself. I'm sure I'm doing something silly here, but can't figure out what.

Below is the class with important methods, including it's Singleton implementation.

 enum UsageType: Int {case ActivationHelp = 0, FullAccessHelp, ContactUs, PrivacyLink}

func usageTypeKey(usage :UsageType) -> String {
    switch usage {
    case .ActivationHelp:
        return "Activation"
    case .FullAccessHelp:
        return "FullAccess"
    case .ContactUs:
        return "ContactUs"
    case .PrivacyLink:
        return "PrivacyLink"
    }
}

class UsageMgr {
    var usageDb = ["default key": "default value"]
    private let formatter : NSDateFormatter?

    //--------------------------------- Init -----------------------------------    
    init() {
        formatter = NSDateFormatter()
        formatter?.dateStyle = .MediumStyle
        formatter?.timeStyle = .MediumStyle
        self.read()
    }

    //--------------------------------- Singletons -----------------------------------
    class var sharedInstance : UsageMgr {
        struct Singleton {
            static let instance = UsageMgr()
        }
        let existingInstance = Singleton.instance
        return existingInstance
    }

    class func markUsed(usage : UsageType) {
        let mgr = UsageMgr.sharedInstance
        mgr.markUsed(usage)
    }

    //--------------------------------- Accessors ------------------------------   
    func markUsed(usage : UsageType) {
        let typeKey = usageTypeKey(usage)
        let now = NSDate()
        let dateString = self.formatter?.stringFromDate(now)
        if let date = dateString {
             self.usageDb[typeKey] = date
        }
    }

    func saveDefaults() {
        let defaults = NSUserDefaults.standardUserDefaults()
        defaults.setObject(usageDb, forKey: "Usage2")
        defaults.synchronize()
      }

    func read() {
        // *** Have to reallocate every time we are re-opened, not sure why
        // Appears usageDb is being freed by ARC
        self.usageDb = [String: String]()
        let defaults = NSUserDefaults.standardUserDefaults()
        let  defaultsUsageOpt = defaults.objectForKey("Usage2") as [String : String]?
        if let usageList = defaultsUsageOpt {
            for (key, dateString) in usageList {
                usageDb[key] = dateString
            }
        }
    }
}

The crash occurs when writing to the Dictionary in the method markUsed (the accessor method called from class method) on the line

    self.usageDb[typeKey] = date

The exact error is EXC_BAD_ACCESS on my line of code, but the bottom of the thread method stack ends at "swift_unknownRelease", which implies to me that the keys/objects in my dictionary has somehow been released. But I've created references to the Singleton and the dictionary in my AppDelegate and crash still occurs.

The debugger is telling me everything is kosher at the crash point

(lldb) po usageDb
 {[0] = (key = "Activation", value = "Mar 25, 2015, 2:35:49 PM")
  [1] = (key = "PrivacyLink", value = "Mar 29, 2015, 8:50:16 PM")
  [2] = (key = "ContactUs", value = "Apr 3, 2015, 12:24:39 PM")
  [3] = (key = "FullAccess", value = "Apr 3, 2015, 12:24:49 PM")
}
(lldb) po typeKey
"FullAccess"

(lldb) po date
"Apr 3, 2015, 12:24:59 PM"

(lldb) po self
0x0000000178039180
 {
  usageDb = {
    [0] = (key = "Activation", value = "Mar 25, 2015, 2:35:49 PM")
    [1] = (key = "PrivacyLink", value = "Mar 29, 2015, 8:50:16 PM")
    [2] = (key = "ContactUs", value = "Apr 3, 2015, 12:24:39 PM")
    [3] = (key = "FullAccess", value = "Apr 3, 2015, 12:24:49 PM")
  }
  formatter = Some {
    Some = 0x0000000178056da0 {
      Foundation.NSFormatter = {
        ObjectiveC.NSObject = {}
      }
    }
  }
}

This is Xcode 6.2, so it's Swift 1.1. I've considered a threading issue, such as multiple readers/writers, but that doesn't make sense given what my code is doing, and I have written a multiple reader/single writer implementation which didn't help. Dictionaries seem to work fine elsewhere in my app, I'm not sure what I'm doing here that is different/bad.

Upvotes: 1

Views: 1217

Answers (1)

SafeFastExpressive
SafeFastExpressive

Reputation: 3805

I've been able to eliminate the crash by calling the UsageMgr read() method in my appDelegate.applicationWillEnterForeground() method. read() allocates a new usage dictionary and reads the previous values from NSUserDefaults.

But I'm not happy with this fix. Clearly it appears ARC is releasing my usage dictionary when the app enters the background, and that's not my understanding of how ARC should work. It's certainly not how it's worked in my objective C applications, many of which have run in the background (while using background fetch, location services, etc). The auto release pool should still exist, and if I have global references to these objects (including the Singleton) I don't think they should be released. But maybe I'm misunderstanding something about ARC or how Swift implements it.

Upvotes: 1

Related Questions