Swift中的简单持久化存储

20

我有一个包含多个属性的对象数组。以下是通过循环遍历对象数组获取的一些示例数据:

Name = Rent
Default Value 750
This Months Estimate = 750
Sum Of This Months Actuals = 0
Risk Factor = 0.0
Monthly Average = 750.0
--------------
Name = Bills
Default Value 250
This Months Estimate = 170
Sum Of This Months Actuals = 140
Risk Factor = 0.0
Monthly Average = 190.0
--------------
Name = Food
Default Value 240
This Months Estimate = 200
Sum Of This Months Actuals = 95
Risk Factor = 0.0
Monthly Average = 190.0
--------------
Name = Lunches
Default Value 100
This Months Estimate = 150
Sum Of This Months Actuals = 155
Risk Factor = 0.899999976158142
Monthly Average = 190.0

数据很少,所以我希望避免使用核心数据。我需要能够持久化保存数组,然后再次打开它并能够遍历它。我希望使用像NSUserDefaults或NSKeyedArchiver这样的简单解决方案,但在Swift中,我无法让它们与此类型的数组一起工作(我已经查阅了24小时的文档、论坛和在线示例...)

您建议我如何持久化保存上述对象数组?或者说,持久化保存这种类型的数组是不好的实践方式吗?

提前感谢您的帮助!

附上对象类:

class costCategory : NSObject {
    var name : String
    var defaultValue : Int
    var thisMonthsEstimate : Int
    var sumOfThisMonthsActuals : Int
    var riskFactor : Float
    var monthlyAverage : Float


    init (name:String, defaultValue:Int, thisMonthsEstimate:Int, sumOfThisMonthsActuals:Int, riskFactor:Float, monthlyAverage:Float) {
        self.name = name
        self.defaultValue = defaultValue
        self.thisMonthsEstimate = thisMonthsEstimate
        self.sumOfThisMonthsActuals = sumOfThisMonthsActuals
        self.riskFactor = riskFactor
        self.monthlyAverage = monthlyAverage
    }

}

如果我尝试将数组保存到NSUserDefaults中,我会遇到以下错误:

Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')

我尝试继承自NSCoder类,但是我遇到了一个无法解决的错误,如下所示:

class costCategory : NSObject, NSCoder {
    var name : String
    var defaultValue : Int
    var thisMonthsEstimate : Int
    var sumOfThisMonthsActuals : Int
    var riskFactor : Float
    var monthlyAverage : Float


    init (name:String, defaultValue:Int, thisMonthsEstimate:Int, sumOfThisMonthsActuals:Int, riskFactor:Float, monthlyAverage:Float) {
        self.name = name
        self.defaultValue = defaultValue
        self.thisMonthsEstimate = thisMonthsEstimate
        self.sumOfThisMonthsActuals = sumOfThisMonthsActuals
        self.riskFactor = riskFactor
        self.monthlyAverage = monthlyAverage
    }



    // MARK: NSCoding

    required convenience init(coder decoder: NSCoder) {
        self.init() //Error here "missing argument for parameter name in call
        self.name = decoder.decodeObjectForKey("name") as String
        self.defaultValue = decoder.decodeIntegerForKey("defaultValue")
        self.thisMonthsEstimate = decoder.decodeIntegerForKey("thisMonthsEstimate")
        self.sumOfThisMonthsActuals = decoder.decodeIntegerForKey("sumOfThisMonthsActuals")
        self.riskFactor = decoder.decodeFloatForKey("riskFactor")
        self.monthlyAverage = decoder.decodeFloatForKey("monthlyAverage")

    }

    func encodeWithCoder(coder: NSCoder) {
        coder.encodeObject(self.name, forKey: "name")
        coder.encodeInt(Int32(self.defaultValue), forKey: "defaultValue")
        coder.encodeInt(Int32(self.thisMonthsEstimate), forKey: "thisMonthsEstimate")
        coder.encodeInt(Int32(self.sumOfThisMonthsActuals), forKey: "sumOfThisMonthsActuals")
        coder.encodeFloat(self.riskFactor, forKey: "riskFactor")
        coder.encodeFloat(self.monthlyAverage, forKey: "monthlyAverage")

    }
}

这是一个类还是值类型? - Kirsteins
使用NSDictionary代替Array怎么样?然后可以使用NSUserDefaults存储NSDictionary。 - David
1
您可能还想包括您尝试解决问题的具体方法。 - Daniel Galasko
它们是Kirsteins非常简单类的实例。我尝试首先创建一个字典数组,但是出现了相同的问题 - 无法持久保存混合对象类型的字典数组... - user3535074
@user3535074请查看答案,不要将其转换为Int32,使用encodeInteger方法! - Daniel Galasko
谢谢!我已经修改了,但是我在主贴末尾提到的错误仍然存在。 - user3535074
2个回答

33

一种可能性是将您的对象属性:值转换为字符串:对象,将它们存储到NSUserDefaults中,然后再获取并解码它们。

如果您想使用NSKeyedArchiver存储您的对象,则您的类需要符合NSCoding协议并且是NSObject的子类。例如:

class costCategory : NSObject, NSCoding {
    var name : String
    var defaultValue : Int
    var thisMonthsEstimate : Int
    var sumOfThisMonthsActuals : Int
    var riskFactor : Float
    var monthlyAverage : Float

    init (name:String, defaultValue:Int, thisMonthsEstimate:Int, sumOfThisMonthsActuals:Int, riskFactor:Float, monthlyAverage:Float) {
        self.name = name
        self.defaultValue = defaultValue
        self.thisMonthsEstimate = thisMonthsEstimate
        self.sumOfThisMonthsActuals = sumOfThisMonthsActuals
        self.riskFactor = riskFactor
        self.monthlyAverage = monthlyAverage
    }

    // MARK: NSCoding

    required init(coder decoder: NSCoder) {
        //Error here "missing argument for parameter name in call
        self.name = decoder.decodeObjectForKey("name") as String
        self.defaultValue = decoder.decodeIntegerForKey("defaultValue")
        self.thisMonthsEstimate = decoder.decodeIntegerForKey("thisMonthsEstimate")
        self.sumOfThisMonthsActuals = decoder.decodeIntegerForKey("sumOfThisMonthsActuals")
        self.riskFactor = decoder.decodeFloatForKey("riskFactor")
        self.monthlyAverage = decoder.decodeFloatForKey("monthlyAverage")
        super.init()
    }

    func encodeWithCoder(coder: NSCoder) {
        coder.encodeObject(self.name, forKey: "name")
        coder.encodeInt(Int32(self.defaultValue), forKey: "defaultValue")
        coder.encodeInt(Int32(self.thisMonthsEstimate), forKey: "thisMonthsEstimate")
        coder.encodeInt(Int32(self.sumOfThisMonthsActuals), forKey: "sumOfThisMonthsActuals")
        coder.encodeFloat(self.riskFactor, forKey: "riskFactor")
        coder.encodeFloat(self.monthlyAverage, forKey: "monthlyAverage")

    }
}

然后您可以将其存档并保存到 NSUserDefaults 中:

let defaults = NSUserDefaults.standardUserDefaults()
let arrayOfObjectsKey = "arrayOfObjectsKey"

var arrayOfObjects = [costCategory]()
var arrayOfObjectsData = NSKeyedArchiver.archivedDataWithRootObject(arrayOfObjects)

defaults.setObject(arrayOfObjectsData, forKey: arrayOfObjectsKey)

// ...

var arrayOfObjectsUnarchivedData = defaults.dataForKey(arrayOfObjectsKey)!
var arrayOfObjectsUnarchived = NSKeyedUnarchiver.unarchiveObjectWithData(arrayOfObjectsUnarchivedData) as [costCategory]

这段代码在Swift中无法编译,你需要移除NSCoding参数的自动解包声明。 - Daniel Galasko
已修复。对不起,没有测试之前在Xcode 6 beta中工作的代码。 - Kirsteins
我也尝试过。在调用自定义初始化程序(包括属性值)时,我遇到了很多问题。我已经将我尝试但无法消除错误的完整代码添加到主帖中(抱歉,我不知道如何在评论中正确格式化)。如果您能看一下并尝试解决这个错误,那就太好了。 - user3535074
谢谢Kirstein -> 这就做到了。这比使用Objective C要复杂得多,但我很感激你的帮助。下次可能会直接考虑使用核心数据,无论数据量大小。使用Swift似乎没有那么多简单的解决方案... - user3535074
如果其中一个变量是对象,如何保存?所以,如果我们有变量person = Person() - Yestay Muratov
@YestayMuratov 让 PersoncostCategory 一样符合 NSCoding。然后使用 encodeObject(,forKey:)decodeObjectForKey 来对其进行编码/解码。 - Kirsteins

6
Core Data非常强大,我强烈建议您不要被它的外观所吓倒。当您的应用程序增长时,使用Core Data来支持数据将使您受益匪浅,因为它可以很好地扩展。
话虽如此,这里有一篇关于使用NSKeyedArchiver基础的不错文章NSHipster。因此,解决方案是让您的对象成为NSObject的子类,并符合NSCoding协议。这样可以将对象从磁盘归档和反归档。然后,您可以将文件保存到文档目录中。
然后,您的子类需要隐式解包所有可编码变量,结果将产生:
class costCategory : NSObject, NSCoding {
var name : String!
var defaultValue : Int!
var thisMonthsEstimate : Int!
var sumOfThisMonthsActuals : Int!
var riskFactor : Float!
var monthlyAverage : Float!


init (name:String, defaultValue:Int, thisMonthsEstimate:Int, sumOfThisMonthsActuals:Int, riskFactor:Float, monthlyAverage:Float) {
    self.name = name
    self.defaultValue = defaultValue
    self.thisMonthsEstimate = thisMonthsEstimate
    self.sumOfThisMonthsActuals = sumOfThisMonthsActuals
    self.riskFactor = riskFactor
    self.monthlyAverage = monthlyAverage
}

override init() {
    super.init()
}

// MARK: NSCoding

required convenience init(coder decoder: NSCoder) {
    self.init()
    self.name = decoder.decodeObjectForKey("name") as String
    self.defaultValue = decoder.decodeIntegerForKey("defaultValue")
    self.thisMonthsEstimate = decoder.decodeIntegerForKey("thisMonthsEstimate")
    self.sumOfThisMonthsActuals = decoder.decodeIntegerForKey("sumOfThisMonthsActuals")
    self.riskFactor = decoder.decodeFloatForKey("riskFactor")
    self.monthlyAverage = decoder.decodeFloatForKey("monthlyAverage")
}

func encodeWithCoder(coder: NSCoder) {
    coder.encodeObject(self.name, forKey: "name")
    coder.encodeInteger((self.defaultValue), forKey: "defaultValue")
    coder.encodeInteger((self.thisMonthsEstimate), forKey: "thisMonthsEstimate")
    coder.encodeInteger((self.sumOfThisMonthsActuals), forKey: "sumOfThisMonthsActuals")
    coder.encodeFloat(self.riskFactor, forKey: "riskFactor")
    coder.encodeFloat(self.monthlyAverage, forKey: "monthlyAverage")
}
}

你可以添加一种方法来查找文档目录的位置:
func documentsDirectory() -> NSString {
    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    let documentDirectory = paths[0] as String
    return documentDirectory
}

那么归档的样子会是这样:

var filePath = documentsDirectory().stringByAppendingPathComponent("fileName")
NSKeyedArchiver.archiveRootObject(receipts, toFile: filePath)

之后,您可以从磁盘中读取数组:

let receipts = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath)

我建议不要使用User Defaults来存储数组,它的作用是存储偏好设置,这里看一下文档:
“NSUserDefaults类提供了一个编程接口,用于与默认系统进行交互。默认系统允许应用程序根据用户的偏好自定义其行为。例如,您可以允许用户确定应用程序显示的测量单位或文档自动保存的频率。应用程序通过将值分配给用户默认数据库中的一组参数来记录此类偏好。”
理想情况下,您不希望持久化数组,因为即使您只需要一个对象,也必须将整个数组提取到内存中。当然,Core Data会为您解决所有不必要的堆混乱:)
编辑
为了使自己更容易进入Core Data,您可以查看Magical Record。他们简化了许多维护Core Data堆栈所需的琐事。
祝你好运
Core Data布道者

感谢您的评论,丹尼尔。恐怕我实际上已经找到了那篇文章,并花了几个小时阅读它并尝试其中的每种方法,但是无论我做什么,在每个点上都会出现错误。实际上,我真的想避免使用核心数据,只是因为我正在尝试(但非常失败)追赶Swift的步伐,并且我想测试一下我到目前为止所学的内容。也许你是对的,也许我应该直接开始使用核心数据... - user3535074
@user3535074,请详细说明您遇到的错误类型是什么? - Daniel Galasko
我更新了我的回答 :) 你真的应该查看NSHipster文章,因为你的对象没有实现他概述的任何方法。 - Daniel Galasko
呵呵,那是因为我在代码中将所有内容都注释掉了。我保证,我已经尝试了几个小时了。我有一个关于“NSKeyedArchiver.archiveRootObject(receipts, toFile: "/path/to/archive")”的问题... 我需要选择一个文件名并在那里指定吗?我确实尝试过这样做,但从未创建过任何文件(至少在xcode中看不到),如果存在但是不可见的话,我也无法从文件中提取对象.. 我现在会再试一次... - user3535074
@user3535074,我更新了我的帖子,详细说明了如何将文件归档 :) - Daniel Galasko
显示剩余2条评论

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