我正在编写一个简单的程序,允许我在iOS应用程序中监控一些销售。 基本上,我需要一个允许客户(作为字符串)的数据结构,对于每个客户,我需要能够保存一个或多个"销售",每个"销售"由销售日期和价格组成。我将其实现为 Swift 字典,该字典在单例 Swift 类中读取并保存到 plist 文件中,因为我需要从多个视图控制器读取和写入相同的数据结构。下面是数据类的代码:
import Foundation
import UIKit
class DataSingleton {
static let sharedDataSingleton = DataSingleton()
private(set) var allData = [String: [AnyObject]]()
private(set) var customers: [String] = []
init() {
let fileURL = self.dataFileURL()
if (NSFileManager.defaultManager().fileExistsAtPath(fileURL.path!)) {
allData = NSDictionary(contentsOfURL: fileURL) as! [String : [AnyObject]]
customers = allData.keys.sort()
}
}
func dataFileURL() -> NSURL {
let url = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return url.first!.URLByAppendingPathComponent("data.plist")
}
func addCustomer(customerName: String) {
if !customers.contains(customerName) {
customers.append(customerName)
allData[customerName] = [[NSDate(), 0]]
saveData()
}
}
func addSale(customerName: String, date: NSDate, price: Int) {
allData[customerName]?.append([date, price])
saveData()
}
func saveData() {
let fileURL = self.dataFileURL()
let customerData = allData as NSDictionary
customerData.writeToURL(fileURL, atomically: true)
}
}
正如你所看到的,我使用了一个字典,其中客户名称是键,销售是AnyObject的数组。我怀疑这是否是实现此方法的最佳和最优雅的方式,所以也许有人可以帮助我解决以下问题:
- 实现
这一点的更好方法是什么?将 sales 实现为结构,然后使用此结构的数组是否有意义?
创建新客户时,我需要使用占位符
allData[customerName] = [[NSDate(), 0]]
因为没有值的字典键不会被保存。有没有更好的方法可以做到这一点?
为什么
allData.keys.sort()
生成字符串数组,而allData.keys
不会?这对我来说没有意义,因为排序函数似乎不应该更改类型。在我的(表)视图中,每个单元格显示一个客户以及每个客户的总销售额。我想按总销售额对这个表进行排序。当前代码(请参阅 3)按字母顺序对客户进行排序。实现此目的的最佳方法是什么:在数据结构本身或视图控制器中按总销售额对客户进行排序?有人可以提供代码如何最优雅地实现这种排序,也许使用排序函数和闭包?
看看你的代码的编辑,看看它是否适合你的目的:
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)
您在评论中的其他问题:
1:希望修复,我放弃了NSCoding
,因为它依赖于NSObject
和NSData
。这意味着我们不能使用具有非常理想的值语义的struct
,以及其他问题。
2:通过扩展添加功能是个好主意。这允许您保持代码分隔,例如,我在extension
中添加了对protocol
PropertyListReadable
的合规性,然后在那里也添加了必要的方法。
3:在此代码中,allData
在init?(propertyListRepresentation:)
中初始化。在那里读取NSDictionary
,并将每个客户及其销售解析为正确的数据结构。由于我将Sale
从class
更改为struct
(多亏了新的protocol
PropertyListReadable
),我们可以声明它所包含的变量,并且init
方法会自动为我们生成init(date:NSDate, price:Int)
像 Sale
这样的struct
比Dictionary
更好,因为类型检查、自动完成、更好地注释struct
和自动生成文档的能力、验证值的可能性、为附加功能添加协议等等。当任何旧Dictionary
被埋在代码中时,很难确切地知道它代表什么,并且有一个具体的struct
、enum
或class
让你的生活变得如此轻松。
Dictionary
非常适合将键映射到值,但是当您想将多个相互依赖的值放在一起时,您希望查看enum
、struct
或class
。