查看您的代码编辑,看看它是否适合您的目的:
import Foundation
/// Allows any object to be converted into and from a NSDictionary
///
/// With thanks to [SonoPlot](https://github.com/SonoPlot/PropertyListSwiftPlayground)
protocol PropertyListReadable {
/// Converts to a NSDictionary
func propertyListRepresentation() -> NSDictionary
/// Initializes from a NSDictionary
init?(propertyListRepresentation:NSDictionary?)
}
/// Converts a plist array to a PropertyListReadable object
func extractValuesFromPropertyListArray<T:PropertyListReadable>(propertyListArray:[AnyObject]?) -> [T] {
guard let encodedArray = propertyListArray else {return []}
return encodedArray.map{$0 as? NSDictionary}.flatMap{T(propertyListRepresentation:$0)}
}
/// Saves a PropertyListReadable object to a URL
func saveObject(object:PropertyListReadable, URL:NSURL) {
if let path = URL.path {
let encoded = object.propertyListRepresentation()
encoded.writeToFile(path, atomically: true)
}
}
/// Holds the information for a sale
struct Sale {
let date:NSDate
let price:Int
}
/// Allows a Sale to be converted to and from an NSDictionary
extension Sale: PropertyListReadable {
/// Convert class to an NSDictionary
func propertyListRepresentation() -> NSDictionary {
let representation:[String:AnyObject] = ["date":self.date, "price":self.price]
return representation
}
/// Initialize class from an NSDictionary
init?(propertyListRepresentation:NSDictionary?) {
guard let values = propertyListRepresentation,
date = values["date"] as? NSDate,
price = values["price"] as? Int else { return nil }
self.init(date:date, price:price)
}
}
/// Singleton that holds all the sale Data
final class DataSingleton {
/// Class variable that returns the singleton
static let sharedDataSingleton = DataSingleton(dataFileURL: dataFileURL)
/// Computed property to get the URL for the data file
static var dataFileURL:NSURL? {
get {
let manager = NSFileManager.defaultManager()
guard let URL = manager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first?.URLByAppendingPathComponent("data.plist"),
path = URL.path else { return nil }
let data = NSKeyedArchiver.archivedDataWithRootObject([String:AnyObject]())
guard manager.fileExistsAtPath(path) &&
manager.createFileAtPath(path, contents: data, attributes: nil) else { return nil }
return URL
}
}
/// The dictionary holding all the sale data
private(set) var allData:[String: [Sale]]
/// Computed property to return all the customers
var customers:[String] { get { return Array(allData.keys) }}
/// Designated initializer
///
/// Made private to disallow additional copies of the singleton
private init() { allData = [:] }
/// Adds a customer to the data dictionary
func addCustomer(customerName: String) {
if allData[customerName] == nil {
allData[customerName] = []
saveData()
}
}
/// Adds a sale to the data dictionary, creating a new customer if neccesary
func addSale(customerName: String, date: NSDate, price: Int) {
addCustomer(customerName)
allData[customerName]?.append(Sale(date: date, price: price))
saveData()
}
// Saves the singleton to the plist file
private func saveData() {
if let fileURL = DataSingleton.dataFileURL {
saveObject(self, URL: fileURL)
}
}
}
/// Allows a DataSingleton to be converted to and from an NSDictionary
extension DataSingleton: PropertyListReadable {
/// Convert class to an NSDictionary
func propertyListRepresentation() -> NSDictionary {
return allData.reduce([String:[AnyObject]]()) {
var retval = $0
retval[$1.0] = $1.1.map {$0.propertyListRepresentation()}
return retval
}
}
/// Initialize class from a plist file
///
/// Made private to disallow additional copies of the singleton
private convenience init?(dataFileURL: NSURL?) {
guard let fileURL = dataFileURL,
path = fileURL.path where (NSFileManager.defaultManager().fileExistsAtPath(path)),
let data = NSDictionary(contentsOfFile: path) else { return nil }
self.init(propertyListRepresentation: data)
}
/// Initialize class from an NSDictionary
convenience init?(propertyListRepresentation:NSDictionary?) {
self.init() // satisfies calling the designated init from a convenience init
guard let values = propertyListRepresentation else {return nil}
allData = values.reduce([:]) {
var retvalue = $0
guard let key = $1.key as? String,
value = $1.value as? [AnyObject] else { return retvalue }
retvalue[key] = extractValuesFromPropertyListArray(value)
return retvalue
}
}
}
用法:
let data = DataSingleton.sharedDataSingleton
data?.addSale("June", date: NSDate(), price: 20)
data?.addSale("June", date: NSDate(), price: 30)
let data2 = DataSingleton.sharedDataSingleton
print(data2?.allData["June"])
// => Optional([Sale(date: 2016-03-10 04:31:49 +0000, price: 20), Sale(date: 2016-03-10 04:31:49 +0000, price: 30)])
要回答3,你应该注意allData.keys的返回值。这是一个惰性集合,它在您要求之前不会提取值。调用sort 会提取所有值,因为没有它们就无法排序。因此,它返回一个常规的、非惰性的Array。你可以在不使用sort 的情况下获得一个非惰性数组,方法是:Array(allData.keys)
您在 cmets 中的其他问题:
1:希望已修复,因为 NSObject 和 NSData 的依赖关系,我从 NSCoding 切换了。这意味着我们不能使用具有非常理想的value semantics 的struct 以及其他问题。
2:通过扩展添加功能是个好主意。这允许您保持代码分隔,例如我在extension 中添加对protocol PropertyListReadable 的合规性,然后在那里也添加必要的方法。
3:在此代码中,allData 在init?(propertyListRepresentation:) 中初始化。在那里读取NSDictionary,并将每个客户及其销售额解析为适当的数据结构。由于我将Sale 从class 更改为struct(感谢新的protocol PropertyListReadable),我们可以只声明它持有的变量,init 方法会自动为我们生成为@ 987654345@
struct 像 Sale 比 Dictionary 更好,因为类型检查、自动完成、更好地注释 struct 和自动生成文档的能力、验证值的可能性、添加附加功能的协议,等等。当旧的Dictionary 隐藏在代码中并拥有具体的struct、enum 或class 时,很难确切地知道它代表什么,这会让你的生活变得如此轻松。
Dictionary 非常适合将键映射到值,但是当您想要将多个相互依赖的值保存在一起时,您需要查看 enum、struct 或 class。