【问题标题】:Initialize subclasses from json从 json 初始化子类
【发布时间】:2017-05-19 09:13:55
【问题描述】:

我正在寻找一种持久化任意子类的好方法。

我在保存时写对象asDictionary to json,在加载时写init(json) them back。结构是Groups,具有不同类型的Units。 Group 只知道它的单位是实现 UnitProtocol 的东西。

Unit 的子类 UA 等具有与 Unit 完全相同的数据。所以数据方面,asDictionaryinit(json) 非常适合Unit。子类仅在逻辑上有所不同。因此,当从文件中恢复它们时,我相信它必须是初始化的确切子类。

我想到的(坏)解决方案

  1. 让每个组都知道它可以拥有不同子类的单元,不只是将它们保存为[UnitProtocol],还可以保存为[UA][UB]等,可以单独保存,由各自的子初始化恢复,并在初始化时合并为[UnitProtocol]
  2. 用它们的类名存储子单元并创建一个Unit.Init(json),它能够以某种方式根据子类型传递初始化。
  3. ??仍在思考,但我相信我必须在这里学到一些东西,以便在不违反单一责任政策的情况下以可维护的方式做到这一点。

【问题讨论】:

  • 您是否有理由避免使用NSKeyedArchiver,它会透明地处理所有这些问题?如果唯一的目标是保存和恢复对象,那么 JSON 似乎做了大量工作却无济于事。
  • 原因:不知道。我该如何使用它?如果你的意思是“为什么是 json?”是因为我喜欢 json 当数据离开 iOS 并变得多云时。
  • 请注意,Swift 4 将有一个更强大的解决方案。请参阅github.com/apple/swift-evolution/blob/master/proposals/…(但几个月左右将无法使用)
  • 我不知道这里的“多云”是什么意思。你真的需要将它发布到需要 JSON 的服务吗? (iCloud 是一个“云”服务,完全不需要 JSON。)

标签: json swift inheritance swift-protocols


【解决方案1】:

对于来自 json 的 init 类,我使用了这个技术:

        //For row in json
        for row in json {
            //namespace it use for init class
            let namespace = (Bundle.main.infoDictionary!["CFBundleExecutable"] as! String).replacingOccurrences(of: " ", with: "_")
            //create instance of your class
            if let myClass = NSClassFromString("\(namespace).\(row.name)") as? NameProtocol.Type{
                //init your class
                let classInit : NameProtocol = myClass.init(myArgument: "Hey")
                //classInit is up for use
            }
        }

【讨论】:

  • 好的,所以使用这种技术我会将命名空间和类名存储在 Unit json 中?
  • @JOG 你只需要存储类名,例如: [ {"name": "MyClassA", "init": "Horus" }, {"name": "MyClassB", "init ": "慢跑" } ]
  • 您能否解释一下您的解决方案中的 NameProtocol.Type 部分?
【解决方案2】:

用它的类名存储每个 Unit json

func asDictionary() -> Dictionary<String, Any> {
    let className = String(describing: type(of: self))

    let d = Dictionary<String, Any>(
        dictionaryLiteral:
        ("className", className),

        ("someUnitData", someUnitData),
        // and so on, for all the data of Unit...

并使用@horusdunord 的解决方案从 json 初始化:

for unitJson in unitsJson {

        let className = (unitJson as! [String: Any])["className"] as? String
        let namespace = (Bundle.main.infoDictionary!["CFBundleExecutable"] as! String).replacingOccurrences(of: " ", with: "_")
        if let unitSubclass = NSClassFromString("\(namespace).\(className ?? "N/A")") as? UnitProtocol.Type {

            if let unit = unitSubclass.init(json: unitJson as! [String : Any]) {
                units.append(unit)
            } else {
                return nil
            }
        } else {
            return nil
        }
    }

这里的技巧是,将类unitSubclass 转换为UnitProtocol 然后允许您调用在该协议中声明但在特定子类中实现的init(json),或者在其超类Unit 中,如果属性是都一样。

【讨论】:

  • 这是非常非常危险的。永远不要根据从 API 收到的数据创建类。这是一个非常经典的漏洞。您应该知道您的系统处理哪些类。创建 JSON 名称到类型的字典。 (如果不清楚,我会写一个例子。)永远不要在你通过网络收到的东西上打电话给NSClassFromString
  • 啊哈,你的意思是检查 className 是否是我期望的类名之一?
  • 是的。否则,您可能会得到一些任意的东西来响应您调用的方法。这在 Java 世界中是一个非常常见的漏洞(从网络中反序列化任意事物很常见)。
  • 令人印象深刻的收获。所以,我需要像 stackoverflow.com/a/42749141/237509 这样的东西来列出所有实现我的 UnitProtocol 的类,对吧?
  • (并且有一种使用“注册”方法更新字典的常见模式,以便类可以注册自己。如果它们是NSObject 子类,那么你可以把它放在@ 987654330@,它会在程序开始时自动运行。但这通常是多余的;同样,如果它非常复杂,你可能有太多的类。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-21
相关资源
最近更新 更多