1

当我尝试在 Swift 4 中使用 Codable 升级和减少代码时,我正在努力理解类/引用类型的行为以及这与更改的关系。

我有两个类——一个包含所有持久数据的超类,我保存到 UserDefaults(带有坐标的地名和字符串),以及一个包含我不需要的额外临时信息(天气数据)的子类对于超类坐标)。

在 Swift 3 中,我曾经这样保存数据:

func saveUserDefaults() {
    var superClassArray = [SuperClass]()
    // subClassArray is of type [SubClass] and contains more data per element.
   superClassArray = subClassArray
    let superClassData = NSKeyedArchiver.archivedData(withRootObject: superClassArray)
    UserDefaults.standard.set(superClassData, forKey: " superClassData")
}

SuperClass 符合 NSObject & NSCoding 它还包括所需的初始化解码器和编码功能。一切都很好。

在尝试切换到 Swift 4 和 codable 时,我修改了 SuperClass 以符合 Codable。SuperClass 现在只有一个基本的初始化程序,并且没有来自 Swift 3 的编码器/解码器的东西。这种新方法不会发生 KeyedArchiving(如下)。子类保持不变。不幸的是,我在尝试的线路上崩溃了?encoder.encode [给线程 1:EXC_BAD_ACCESS (code=1, address=0x10)]。我的假设是编码器与相同的引用类型混淆,其中一个是 SuperClass 和一个 SubClass(s​​ubClassArray[0] === superClassArray[0] 是真的)。我认为这可能有效:

func saveUserDefaults() {
   var superClassArray = [SuperClass]()
    superClassArray = subClassArray
    // assumption was that the subclass would only contain parts of the superclass & wouldn't produce an error when being encoded
    let encoder = JSONEncoder()
    if let encoded = try? encoder.encode(superClassArray){
        UserDefaults.standard.set(encoded, forKey: " superClassArray ")
    } else {
        print("Save didn't work!")
    }
}

然后,不是创建一个空的 superClassArray,而是使用:superClassArray = subClassArray,如上所示,我将其替换为单行:

let superClassArray: [SuperClass] = subClassArray.map{SuperClass(name:  $0.name, coordinates: $0.coordinates)}

这行得通。同样,假设是因为我传入了类引用类型中的值,并且没有创建 superClassArray = subClassArray。此外,正如预期的那样, subClassArray[0] === superClassArray[0] 为假

那么为什么 Swift 3 中的“旧东西”可以工作,即使我在 let superClassData = NSKeyedArchiver.archivedData(withRootObject: superClassArray) 之前使用了 superClassArray = subClassArray 行?通过在 Swift 4 中创建与旧的 Swift 3 编码器/解码器相同的数组,我是否基本上实现了相同的结果?是循环/娱乐

谢谢!

4

1 回答 1

4

多态持久性似乎被设计破坏了

错误报告SR-5331引用了他们在雷达上得到的响应。

与现有的 NSCoding API (NSKeyedArchiver) 不同,为了灵活性和安全性,新的 Swift 4 Codable 实现不会将有关编码类型的类型信息写入生成的档案中。因此,在解码时,API 只能使用您提供的具体类型来解码值(在您的情况下是超类类型)。

这是设计使然——如果您需要执行此操作所需的动力,我们建议您采用 NSSecureCoding 并使用 NSKeyedArchiver/NSKeyedUnarchiver

我不为所动,从所有热情洋溢的文章中想到 Codable 是我的一些祈祷的答案。作为对象工厂的一组并行 Codable 结构是我正在考虑的一种解决方法,以保留类型信息。

更新我已经使用管理重新创建多态类的单个结构编写了一个示例。在GitHub上可用。

无法让它轻松地与子类化一起工作。但是,符合基本协议的类可以申请Codable默认编码。回购包含键控和非键控方法。比较简单的不加键,复制在下面

// Demo of a polymorphic hierarchy of different classes implementing a protocol
// and still being Codable
// This variant uses unkeyed containers so less data is pushed into the encoded form.
import Foundation

protocol BaseBeast  {
  func move() -> String
  func type() -> Int
  var name: String { get }
}

class DumbBeast : BaseBeast, Codable  {
  static let polyType = 0
  func type() -> Int { return DumbBeast.polyType }

  var name:String
  init(name:String) { self.name = name }
  func move() -> String { return "\(name) Sits there looking stupid" }
}

class Flyer : BaseBeast, Codable {
  static let polyType = 1
  func type() -> Int { return Flyer.polyType }

  var name:String
  let maxAltitude:Int
  init(name:String, maxAltitude:Int) {
    self.maxAltitude = maxAltitude
    self.name = name
  }
  func move() -> String { return "\(name) Flies up to \(maxAltitude)"}
}


class Walker : BaseBeast, Codable {
  static let polyType = 2
  func type() -> Int { return Walker.polyType }

  var name:String
  let numLegs: Int
  let hasTail: Bool
  init(name:String, legs:Int=4, hasTail:Bool=true) {
    self.numLegs = legs
    self.hasTail = hasTail
    self.name = name
  }
  func move() -> String {
    if numLegs == 0 {
      return "\(name) Wriggles on its belly"
    }
    let maybeWaggle = hasTail ? "wagging its tail" : ""
    return "\(name) Runs on \(numLegs) legs \(maybeWaggle)"
  }
}

// Uses an explicit index we decode first, to select factory function used to decode polymorphic type
// This is in contrast to the current "traditional" method where decoding is attempted and fails for each type
// This pattern of "leading type code" can be used in more general encoding situations, not just with Codable
//: **WARNING** there is one vulnerable practice here - we rely on the BaseBeast types having a typeCode which
//: is a valid index into the arrays `encoders` and `factories`
struct CodableRef : Codable {
  let refTo:BaseBeast  //In C++ would use an operator to transparently cast CodableRef to BaseBeast

  typealias EncContainer = UnkeyedEncodingContainer
  typealias DecContainer = UnkeyedDecodingContainer
  typealias BeastEnc = (inout EncContainer, BaseBeast) throws -> ()
  typealias BeastDec = (inout DecContainer) throws -> BaseBeast

  static var encoders:[BeastEnc] = [
    {(e, b) in try e.encode(b as! DumbBeast)},
    {(e, b) in try e.encode(b as! Flyer)},
    {(e, b) in try e.encode(b as! Walker)}
  ]

  static var factories:[BeastDec] = [
    {(d) in try d.decode(DumbBeast.self)},
    {(d) in try d.decode(Flyer.self)},
    {(d) in try d.decode(Walker.self)}
  ]

  init(refTo:BaseBeast) {
    self.refTo = refTo
  }

  init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()
    let typeCode = try container.decode(Int.self)
    self.refTo = try CodableRef.factories[typeCode](&container)
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.unkeyedContainer()
    let typeCode = self.refTo.type()
    try container.encode(typeCode)
    try CodableRef.encoders[typeCode](&container, refTo)
  }
}


struct Zoo : Codable {
  var creatures = [CodableRef]()
  init(creatures:[BaseBeast]) {
    self.creatures = creatures.map {CodableRef(refTo:$0)}
  }
  func dump() {
    creatures.forEach { print($0.refTo.move()) }
  }
}


//: ---- Demo of encoding and decoding working ----
let startZoo = Zoo(creatures: [
  DumbBeast(name:"Rock"),
  Flyer(name:"Kookaburra", maxAltitude:5000),
  Walker(name:"Snake", legs:0),
  Walker(name:"Doggie", legs:4),
  Walker(name:"Geek", legs:2, hasTail:false)
  ])


startZoo.dump()
print("---------\ntesting JSON\n")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encData = try encoder.encode(startZoo)
print(String(data:encData, encoding:.utf8)!)
let decodedZoo = try JSONDecoder().decode(Zoo.self, from: encData)

print ("\n------------\nAfter decoding")

decodedZoo.dump()

更新 2020-04 经验

与使用 Codable 相比,这种方法继续更加灵活和优越,但会花费更多的程序员时间。它在 Touchgram 应用程序中被大量使用,该应用程序在 iMessage 中提供了丰富的交互式文档。

在那里,我需要编码多个多态层次结构,包括不同的传感器和动作。通过存储解码器的签名,它不仅提供了子类化,还允许我将旧解码器保留在代码库中,以便旧消息仍然兼容。

于 2018-02-16T16:17:31.327 回答