【问题标题】:Swift Dictionary of Arrays, Data StructureSwift 数组字典,数据结构
【发布时间】:2016-06-21 09:22:59
【问题描述】:

我正在编写一个简单的程序,允许我监控 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 数组。我怀疑这是否是实现这一点的最佳和最优雅的方式,所以也许有人可以帮助我解决以下问题:

  1. 什么是更好的实现方式?将 sales 实现为结构然后使用该结构的数组是否有意义?

  2. 创建新客户时,我需要使用占位符

    allData[customerName] = [[NSDate(), 0]]
    

因为没有值的字典键不会被保存。有没有更好的方法来做到这一点?

  1. 为什么allData.keys.sort() 生成字符串数组,而allData.keys 不生成?这对我来说没有意义,因为排序函数似乎不应该改变类型。

  2. 在我的(表格)视图中,每个单元格显示一个客户,其中包含每个客户的总销售额。我想按销售额总和对该表进行排序。当前代码(参见 3.)按字母顺序对客户进行排序。实现这一点的最佳方法是什么:按数据结构本身或视图控制器中的总销售额对客户进行排序?有人可以提供代码如何最优雅地实现这种排序,也许使用 sort 函数和闭包?

【问题讨论】:

    标签: arrays swift sorting dictionary data-structures


    【解决方案1】:

    查看您的代码编辑,看看它是否适合您的目的:

    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:希望已修复,因为 NSObjectNSData 的依赖关系,我从 NSCoding 切换了。这意味着我们不能使用具有非常理想的value semanticsstruct 以及其他问题。

    2:通过扩展添加功能是个好主意。这允许您保持代码分隔,例如我在extension 中添加对protocol PropertyListReadable 的合规性,然后在那里也添加必要的方法。

    3:在此代码中,allDatainit?(propertyListRepresentation:) 中初始化。在那里读取NSDictionary,并将每个客户及其销售额解析为适当的数据结构。由于我将Saleclass 更改为struct(感谢新的protocol PropertyListReadable),我们可以只声明它持有的变量,init 方法会自动为我们生成为@ 987654345@

    structSaleDictionary 更好,因为类型检查、自动完成、更好地注释 struct 和自动生成文档的能力、验证值的可能性、添加附加功能的协议,等等。当旧的Dictionary 隐藏在代码中并拥有具体的structenumclass 时,很难确切地知道它代表什么,这会让你的生活变得如此轻松。

    Dictionary 非常适合将键映射到值,但是当您想要将多个相互依赖的值保存在一起时,您需要查看 enumstructclass

    【讨论】:

    • 嗨,肯尼斯。非常感谢你的帮助。关于您的代码的几个问题: 1. 扩展无法编译,不符合 NSCoding 协议 2. 为什么首先要扩展而不在 Sale 类定义中定义它? 3、DataSingleton中allData的初始化器不见了。还有为什么要在init方法下面初始化Sale的成员呢?一般来说:如果你能写几句话,你为什么选择这个数据结构以及为什么它比我的原始代码更好的设计,那就太好了。并且对我关于排序的问题 4 的帮助会很棒......
    • 抱歉没有解释。我本来打算把它贴出来,但有一些紧急的事情让我离开了它。我会尽快回答你的问题,最有可能几个小时。
    • @MaxiMus 我已经更新了代码,看看它是否适合你。我切换了序列化方法,因为它可以使用struct 而不是class 来代替Sale。我还修复了一个错误,如果 plist 文件没有正确创建,它将失败。添加了一些cmets,有什么问题可以问。 cmets 将允许您选择单击任何类或函数并获取更多信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-12
    • 1970-01-01
    • 1970-01-01
    • 2014-07-26
    • 2013-12-10
    相关资源
    最近更新 更多