参考类型/子类,以及对Swift 4 Codable&编码器/解码器 [英] Reference Types/Subclassing, and Changes to Swift 4 Codable & encoder/decoders

查看:137
本文介绍了参考类型/子类,以及对Swift 4 Codable&编码器/解码器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm struggling to understand class/reference type behavior and how this relates to changes as I try to upgrade and reduce code using Codable in Swift 4.

我有两个类-一个具有所有持久数据并将其保存到UserDefaults(带有坐标的地名和字符串)的超类,以及一个子类,其中包含我没有的其他临时信息需要(SuperClass坐标的天气数据).

I have two classes – a SuperClass with all of the data that will be persistent and that I save to UserDefaults (a place name & string with coordinates), and a SubClass that contains additional, temporary info that I don't need (weather data for the SuperClass coordinates).

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

In Swift 3 I used to save data like this:

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& NS编码 它还包括所需的init解码器和编码功能. 一切正常.

SuperClass conformed to NSObject & NSCoding It also included the required init decoder & the encode function. It all worked fine.

尝试切换到Swift 4& codable我已经修改了SuperClass以符合Codable. 现在,SuperClass仅具有一个基本的初始化程序,而Swift 3中没有任何编码器/解码器.这种新方法(下文)没有发生KeyedArchiving.子类保持不变.不幸的是,我在尝试的线路上崩溃了? encoder.encode [给出线程1:EXC_BAD_ACCESS(代码= 1,地址= 0x10)].我的假设是,编码器会与相同的引用类型(其中一个是SuperClass而一个是SubClass)混淆(subClassArray [0] === superClassArray [0]为true). 我认为这可能有效:

In trying to switch to Swift 4 & codable I've modified SuperClass to conform to Codable. SuperClass now only has one basic initializer and none of the encoder/decoder stuff from Swift 3. There is no KeyedArchiving happening with this new approach (below). SubClass remains unchanged. Unfortunately I crash on the line where I try? encoder.encode [giving a Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)]. My assumption is that the encoder is getting confused with identical reference types where one is SuperClass and one SubClass (subClassArray[0] === superClassArray[0] is true). I thought this might work:

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,如上所示,我将其替换为单行:

Then, instead of creating an empty superClassArray, then using: superClassArray = subClassArray, as shown above, I replace this with the single line:

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

这有效.同样,假设是因为我要在类引用类型&的内部传递值.还没有做superClassArray = subClassArray.而且,正如预期的那样,subClassArray [0] === superClassArray [0]为假

This works. Again, assumption is because I'm passing in the values inside of the class reference type & haven't made the superClassArray = subClassArray. Also, as expected, subClassArray[0] === superClassArray[0] is false

所以,即使我在let superClassData = NSKeyedArchiver.archivedData(withRootObject:superClassArray)之前使用了superClassArray = subClassArray行,为什么Swift 3中的旧东西"仍能正常工作 ?通过在Swift 4中创建旧的Swift 3编码器/解码器中的数组,我是否实质上达到了相同的结果?是循环播放/休闲娱乐

So why did the "old stuff" in Swift 3 work, even though I used the line superClassArray = subClassArray before the let superClassData = NSKeyedArchiver.archivedData(withRootObject: superClassArray) ? Am I essentially achieving the same result by creating the array in Swift 4 that was happening with the old Swift 3 encoder/decoder? Is the looping / recreation

谢谢!

推荐答案

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

Polymorphic persistence appears to be broken by design.

错误报告 SR-5331 引用了他们对雷达.

The bug report SR-5331 quotes the response they got on their Radar.

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

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

我从所有发光的文章中都感到惊讶,可编码是我某些祈祷的答案.我正在考虑使用一组并行的Codable结构作为对象工厂,以保留类型信息.

I am unimpressed, having thought from all the glowing articles that Codable was the answer to some of my prayers. A parallel set of Codable structs that act as object factories is one workaround I'm considering, to preserve type information.

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

Update I have written a sample using a single struct that manages recreating polymorphic classes. Available on GitHub.

不能让它轻松地与子类一起使用.但是,符合基本协议的类可以将Codable应用于默认编码.回购包含键控和非键控方法.比较简单的是未加密,复制如下

I was not able to get it to work easily with subclassing. However, classes that conform to a base protocol can apply Codable for default encoding. The repo contains both keyed and unkeyed approaches. The simpler is unkeyed, copied below

// 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内部提供了丰富的交互式文档.

Update 2020-04 experience

This approach continues to be more flexible and superior to using Codable, at the cost of a bit more programmer time. It is used very heavily in the Touchgram app which provides rich, interactive documents inside iMessage.

在那里,我需要对多个多态层次进行编码,包括不同的Sensor和Action.通过存储解码器的签名,它不仅提供了子类,而且还允许我将较旧的解码器保留在代码库中,从而使旧消息仍然兼容.

There, I need to encode multiple polymorphic hierarchies, including different Sensors and Actions. By storing signatures of decoders, it not only provides with subclassing but also allows me to keep older decoders in the code base so old messages are still compatible.

这篇关于参考类型/子类,以及对Swift 4 Codable&编码器/解码器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆