web-dev-qa-db-ja.com

コアデータでSwift 4 Codableを使用する方法

Codableは非常にエキサイティングな機能のようです。しかし、Core Dataでどのように使用できるのでしょうか?特に、NSManagedObjectとの間でJSONを直接エンコード/デコードすることは可能ですか?

私は非常に簡単な例を試しました:

enter image description here

定義済みFoo自分:

import CoreData

@objc(Foo)
public class Foo: NSManagedObject, Codable {}

しかし、次のように使用する場合:

let json = """
{
    "name": "foo",
    "bars": [{
        "name": "bar1",
    }], [{
        "name": "bar2"
    }]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let foo = try! decoder.decode(Foo.self, from: json)
print(foo)

コンパイラは次のエラーで失敗しました:

super.init isn't called on all paths before returning from initializer

ターゲットファイルはFooを定義したファイルでした

NSManagedObjectContextも渡さなかったので、おそらく間違っていたと思いますが、どこに貼り付けるのかわかりません。

Core DataはCodableをサポートしていますか?

42
hgl

CodableインターフェースをCoreDataオブジェクトで使用してデータをエンコードおよびデコードできますが、プレーンなSwiftオブジェクトで使用する場合ほど自動ではありません。コアデータオブジェクトを使用してJSONデコードを直接実装する方法は次のとおりです。

まず、オブジェクトにCodableを実装させます。このインターフェイスは、拡張機能ではなく、オブジェクトで定義する必要があります。このクラスでコーディングキーを定義することもできます。

class MyManagedObject: NSManagedObject, Codable {
    @NSManaged var property: String?

    enum CodingKeys: String, CodingKey {
       case property = "json_key"
    }
}

次に、initメソッドを定義できます。 Decodableプロトコルではinitメソッドが必要であるため、これもクラスメソッドで定義する必要があります。

required convenience init(from decoder: Decoder) throws {
}

ただし、管理対象オブジェクトで使用する適切な初期化子は次のとおりです。

NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)

したがって、ここでの秘密は、userInfo辞書を使用して、適切なコンテキストオブジェクトを初期化子に渡すことです。これを行うには、CodingUserInfoKey構造体を新しいキーで拡張する必要があります。

extension CodingUserInfoKey {
   static let context = CodingUserInfoKey(rawValue: "context")
}

これで、コンテキストのデコーダーとして使用できます。

required convenience init(from decoder: Decoder) throws {

    guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
    guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() }

    self.init(entity: entity, in: context)

    let container = decoder.container(keyedBy: CodingKeys.self)
    self.property = container.decodeIfPresent(String.self, forKey: .property)
}

ここで、管理対象オブジェクトのデコードを設定するときに、適切なコンテキストオブジェクトを渡す必要があります。

let data = //raw json data in Data object
let context = persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[.context] = context

_ = try decoder.decode(MyManagedObject.self, from: data) //we'll get the value from another context using a fetch request later...

try context.save() //make sure to save your data once decoding is complete

データをエンコードするには、encodeプロトコル関数を使用して同様のことを行う必要があります。

68
casademora

CoreDataは独自の永続化フレームワークであり、その詳細なドキュメントに従って、指定されたイニシャライザーを使用し、かなり具体的なパスに従ってオブジェクトを作成および保存する必要があります。

ただし、Codableを使用できるのと同じように、限られた方法でNSCodingを使用できます。

1つの方法は、これらのプロトコルのいずれかでオブジェクト(または構造体)をデコードし、そのプロパティをCore Dataのドキュメントごとに作成した新しいNSManagedObjectインスタンスに転送することです。

別の方法(非常に一般的)は、管理対象オブジェクトのプロパティに格納する非標準オブジェクトにのみプロトコルの1つを使用することです。 「非標準」とは、モデルで指定されているCore Dataの標準属性タイプに準拠していないものを意味します。たとえば、NSColorは、CDがサポートする基本的な属性タイプの1つではないため、Managed Objectプロパティとして直接保存できません。代わりに、NSKeyedArchiverを使用して色をNSDataインスタンスにシリアル化し、それを管理オブジェクトのDataプロパティとして保存できます。 NSKeyedUnarchiverでこのプロセスを逆にします。これは単純化されており、コアデータを使用してこれを実行するより良い方法があります( 一時的な属性 を参照)が、それは私のポイントを示しています。

おそらくEncodableCodableを構成する2つのプロトコルの1つ-他の名前を推測できますか?)を採用して、共有するためにManaged Objectインスタンスを直接JSONに変換することもできますが、 コーディングキーの指定 および独自のカスタムencode実装。カスタムコーディングキーを使用してコンパイラによって自動合成されないためです。この場合、含めるonlyキー(プロパティ)のみを指定します。

お役に立てれば。

12
Joshua Nozzi

Swift 4.2:

Casademoraのソリューションに従って、

guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }

あるべき

guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }

これにより、Xcodeが配列スライスの問題として誤って認識するエラーを防ぎます。

編集:暗黙的にアンラップされたオプションを使用して、使用されるたびにアンラップ.contextを強制する必要を取り除きます。

6
Schemetrical

XCodeのNSManagedObjectファイル生成に対する最新のアプローチを利用したい人のための代替手段として、DecoderWrapperクラスを作成してDecoderオブジェクトを公開し、それをオブジェクト内で使用しますJSONDecodingプロトコルに準拠しています。

class DecoderWrapper: Decodable {

    let decoder:Decoder

    required init(from decoder:Decoder) throws {

        self.decoder = decoder
    }
}

protocol JSONDecoding {
     func decodeWith(_ decoder: Decoder) throws
}

extension JSONDecoding where Self:NSManagedObject {

    func decode(json:[String:Any]) throws {

        let data = try JSONSerialization.data(withJSONObject: json, options: [])
        let wrapper = try JSONDecoder().decode(DecoderWrapper.self, from: data)
        try decodeWith(wrapper.decoder)
    }
}

extension MyCoreDataClass: JSONDecoding {

    enum CodingKeys: String, CodingKey {
        case name // For example
    }

    func decodeWith(_ decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.name = try container.decode(String.self, forKey: .name)
    }
}

これはおそらく、オプションでない属性のないモデルにのみ役立ちますが、Decodableを使用したいという私の問題を解決しますが、すべてのクラス/プロパティを手動で作成することなく、コアデータとの関係と永続性も管理します。

2
ChrisH