简体   繁体   中英

Persist an array of custom structs in Swift

I'm trying to store an array of custom structs.

In my attempt below I get a run-time error in the second line of saveSampleArrayOfQuerySettings() complaining about casting to AnyObject.

struct QuerySettings {

    //    let ARRAY_INDEX_WHERE_TO_SAVE_STRUCT = 0

    let QUERY_SETTINGS_KEY = "querysettings"
    let defaults = NSUserDefaults.standardUserDefaults()

    private var _includeCompletedReminders: Bool = false        // Default value
    var includeCompletedReminders: Bool {
        get {
            // If value has been set before then use it otherwise retrieve it.
            return _includeCompletedReminders
        } set (newVal) {
            _includeCompletedReminders = newVal
            //            saveSettings(self, index: ARRAY_INDEX_WHERE_TO_SAVE_STRUCT, saveKey: QUERY_SETTINGS_KEY)
        }
    }

    private var _includeRemindersWithNoDueDate: Bool = true
    var includeRemindersWithNoDueDate: Bool {
        get {
            return _includeRemindersWithNoDueDate
        } set (newVal) {
            _includeRemindersWithNoDueDate = newVal
            //            saveSettings(self, index: ARRAY_INDEX_WHERE_TO_SAVE_STRUCT, saveKey: QUERY_SETTINGS_KEY)
        }
    }
}

func saveSampleArrayOfQuerySettings(saveKey: String) {
    let sampleArray = [QuerySettings(), QuerySettings()]

    // Persist
    let archivedSettingsArray = NSKeyedArchiver.archivedDataWithRootObject(sampleArray as! AnyObject)

    // RUNTIME ERROR AT PREVIOUS LINE: "Could not cast value of type 'Swift.Array<RemindersPro.QuerySettings>' (0x112df10d8) to 'Swift.AnyObject' (0x1169d6018)."

    NSUserDefaults.standardUserDefaults().setObject(archivedSettingsArray, forKey: saveKey)
    NSUserDefaults.standardUserDefaults().synchronize()

    // For Testing Purposes - Load the saved settings
    if let retrievedArchivedSettingsArray : AnyObject = NSUserDefaults.standardUserDefaults().objectForKey(saveKey) {
        if let settingsArray : AnyObject = NSKeyedUnarchiver.unarchiveObjectWithData(retrievedArchivedSettingsArray as! NSData) {
            let arr = settingsArray as! [QuerySettings]
            print(arr.first)
        }
    }
}

saveSampleArrayOfQuerySettings("saveKey")

I wonder whether I need to encode my struct. I checked out these posts but oddly couldn't even get the samples in the posts working (let alone applying them to my code.)

How to save an array of objects to NSUserDefault with swift?

(Swift) Storing and retrieving Array to NSUserDefaults

How to save an array of custom structs to plist swift

Can you please let me know how to get this working?

Your code is too verbose:

  1. You don't need to explicit define type. Assign a default value of true or false is enough for the compiler to deduce that you want Bool . Swift is built around the idea that the compiler can be made very smart to lighten the on the programmer.
  2. Your custom getters and setters do nothing special so it's best to let Swift auto-synthesize it.

Now, on to the topic of archiving: you will be dismayed to learn that NSCoding is only available to object which inherits from NSObject . Structs can not be made to conform to NSCoding . You need to wrap it around in a helper class.


Here's what I came up with:

1. Define a helper class to serialize structs:

protocol ArchivableStruct {
    var dataDictionary: [String: AnyObject] { get }
    init(dataDictionary aDict: [String: AnyObject])
}

class StructArchiver<T: ArchivableStruct>: NSObject, NSCoding {
    var structValue: T

    init(structValue: T) {
        self.structValue = structValue
    }

    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(self.structValue.dataDictionary, forKey: "dataDictionary")
    }

    required init?(coder aDecoder: NSCoder) {
        let dataDictionary = aDecoder.decodeObjectForKey("dataDictionary") as! [String: AnyObject]
        self.structValue = T(dataDictionary: dataDictionary)
    }
}

2. Export data from the struct into a Dictionary

The dictionary is what gets archived. On the way back, we simply unpack this Dictionary to get the struct. We don't need to worry about QUERY_SETTINGS_KEY and defaults because the former is a constant and the later has its own data source ( NSUserDefaults ).

struct QuerySettings: ArchivableStruct {
    let QUERY_SETTINGS_KEY = "querysettings"
    let defaults = NSUserDefaults.standardUserDefaults()

    var includeCompletedReminders = false
    var includeRemindersWithNoDueDate = true

    init(includeCompletedReminders: Bool, includeRemindersWithNoDueDate: Bool) {
        self.includeCompletedReminders = includeCompletedReminders
        self.includeRemindersWithNoDueDate = includeRemindersWithNoDueDate
    }

    // --------------------------------------
    // ArchivableStruct protocol
    // --------------------------------------
    var dataDictionary: [String: AnyObject] {
        get {
            return [
                "includeCompletedReminders": self.includeCompletedReminders,
                "includeRemindersWithNoDueDate": self.includeRemindersWithNoDueDate
            ]
        }
    }

    init(dataDictionary aDict: [String : AnyObject]) {
        self.includeCompletedReminders = aDict["includeCompletedReminders"] as! Bool
        self.includeRemindersWithNoDueDate = aDict["includeRemindersWithNoDueDate"] as! Bool
    }
}

3. Mapping from struct to the helper class

Since you are not archiving a QuerySetting struct but an array of them, there is an extra step to map it to StructArchiver<T> so that your array can be archived:

// Set up a sample array
let a = QuerySettings(includeCompletedReminders: true, includeRemindersWithNoDueDate: false)
let b = QuerySettings(includeCompletedReminders: false, includeRemindersWithNoDueDate: true)
let sampleArray = [a, b]

// Archive
let wrapperArray = sampleArray.map { StructArchiver(structValue: $0) }
let data = NSKeyedArchiver.archivedDataWithRootObject(wrapperArray)

// Unarchive
let unwrapperArray = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [StructArchiver<QuerySettings>]
let unarchivedArray = unwrapperArray.map { $0.structValue }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM