在Swift中持久化自定义结构体数组

3
我想存储一个自定义结构体的数组。
在下面的尝试中,我在saveSampleArrayOfQuerySettings()的第二行得到了运行时错误,抱怨转换为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")

我想知道是否需要对我的结构体进行编码。我查看了这些帖子,但奇怪的是甚至无法使这些帖子中的样例工作(更不用说将它们应用到我的代码中了)。
请问如何让它正常工作呢?以下是需要查看的帖子:
- 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 谢谢。

http://stackoverflow.com/questions/33192675/using-nsuserdefaults-on-arrays/33194374?s=2|0.0583#33194374 - Leo Dabus
你需要创建一个符合 NSCoding 协议的类,并将其保存为 NSData。 - Leo Dabus
1个回答

4

你的代码太冗长了:

  1. 不需要显式定义类型。为编译器赋一个默认值 truefalse 就足以推断出你想要的是 Bool。 Swift 的设计理念是,编译器可以变得非常智能,减轻程序员的负担。
  2. 你的自定义 getter 和 setter 没有什么特别之处,所以最好让 Swift 自动合成它们。

现在,进入存档的主题:你会惊讶地发现,只有从 NSObject 继承的对象才能使用 NSCoding。 结构体无法使其符合 NSCoding。 你需要在辅助类中进行包装。


以下是我想到的方法:

1. 定义一个辅助类来序列化结构体:

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. 从结构体中将数据导出到字典

字典是需要进行归档的内容。在恢复时,我们只需解压缩此字典即可获取结构体。我们无需担心QUERY_SETTINGS_KEYdefaults,因为前者是一个常量,后者有自己的数据源(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. 从struct映射到帮助类

由于你要归档的不是一个QuerySetting结构体,而是数组,所以需要进行额外的映射步骤,将其映射到StructArchiver<T>,以便对你的数组进行归档:

// 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 }

感谢详细的回答!(代码似乎有点冗长,因为我注释掉了saveSettings()调用-每当我更改变量时,它会自动持久化。)但是,我决定将我的结构体改为类。这似乎是一个不那么复杂的解决方案。我很失望结构体似乎比类低一等公民。(无意冒犯。)虽然核心语言支持它,但似乎框架希望你走另一条路-类、协议等。我想遵循函数式编程风格,它更喜欢结构体。感觉像逆流而上... - Daniel
1
@Daniel NSCoder非常古老,因此在ObjC中根深蒂固。在C和ObjC中,struct远不及Swift强大。苹果公司面临着更新Cocoa库以跟上Swift功能的巨大任务。尽管如此,NSCoder确实支持一些结构体,例如 CGPointCGSize - Code Different

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接