2017-10-30 17 views
1

Swift 4でCodableを使用してコードをアップグレードしたり減らしたりする際に、クラス/参照型の動作がどのように変化するのか分かりません。リファレンスタイプ/サブクラス化、Swiftへの変更

私は2つのクラスを持っています - 永続化されるすべてのデータを持つSuperClassと、UserDefaults(場所の名前は座標で&の文字列)に保存されます。また、必要のない追加の一時的な情報を含むSubClass (SuperClass座標の気象データ)。スイフト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") 
} 

スーパークラスまた、エンコード機能に必要な初期化デコーダ&を含まNSCoding NSObjectの&に準拠。 それはすべてうまくいきました。

スウィフト4に切り替える際に、&コード可能です。私はSuperClassをCodableに合わせて変更しました。 SuperClassには、基本的なイニシャライザが1つしかなく、Swift 3のエンコーダ/デコーダのものはありません。この新しいアプローチでは、KeyedArchivingは起こりません(下記参照)。 SubClassは変更されません。残念ながら、私は試しているラインでクラッシュしますか? encoder.encode [スレッド1を与える:EXC_BAD_ACCESS(コード= 1、アドレス= 0x10)]。私の前提は、エンコーダが1つのSuperClassと1つの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]は偽です

Swipe 3の "古いもの"は、私がletスーパークラスデータ= NSKeyedArchiverの前にsuperClassArray = subClassArrayという行を使用していたのに、 .archivedData(withRootObject:superClassArray) ? Swift 4で古いSwift 3エンコーダ/デコーダで起こっていたアレイを作成することによって、私は本質的に同じ結果を達成しましたか?ループ/レクリエーションです

ありがとう!

+0

「JSONEncoder」によってスローされたエラーをキャッチして、それを印刷します。なぜエンコーディングが失敗しているのかがわかります。また、クラスの定義(または問題を示す簡単なバージョン)を含めることができれば、誰かがあなたを助けることができる可能性が高くなります! – idz

答えて

1

多型の持続性は、がデザインによって壊れているように見える

のために、彼らは生成されたアーカイブにエンコードの種類についての彼らのRadar.既存NSCodingのAPI(NSKeyedArchiver)とは異なり

、新しいスウィフト4コード可能な実装が出て書いていない種類の情報を得た応答を引用SR-5331バグレポート柔軟性とセキュリティの両方を提供します。このように、デコード時には、APIは値をデコードするために提供する具体的な型(あなたの場合はスーパークラス型)のみを使用できます。

このは仕様です - あなたがこれを行うために必要なダイナミズムを必要とする場合、我々はあなたがNSKeyedArchiver/NSKeyedUnarchiverをNSSecureCodingを採用し、使用することをお勧めします、私は感動、すべての輝く記事から思ったのです

そのコード可能ないくつかの私の祈りに対する答えでした。オブジェクトファクトリとして機能するコーディング可能な構造体の並列セットは、タイプ情報を保持するために私が検討している1つの回避策です。

更新多型の再現を管理する単一の構造体を使用してサンプルを作成しました。利用可能なのはGitHubです。

私はではありませんでした。は、サブクラス化で簡単に動作させることができませんでした。ただし、基本プロトコルに準拠するクラスでは、デフォルトのエンコード用にCodableを適用できます。レポには、キー付きおよび非キー付きの両方のアプローチが含まれています。より簡単です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() 
関連する問題