Codableは非常にエキサイティングな機能のようです。しかし、Core Dataでどのように使用できるのでしょうか?特に、NSManagedObjectとの間でJSONを直接エンコード/デコードすることは可能ですか?
私は非常に簡単な例を試しました:
定義済み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
をサポートしていますか?
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プロトコル関数を使用して同様のことを行う必要があります。
CoreDataは独自の永続化フレームワークであり、その詳細なドキュメントに従って、指定されたイニシャライザーを使用し、かなり具体的なパスに従ってオブジェクトを作成および保存する必要があります。
ただし、Codable
を使用できるのと同じように、限られた方法でNSCoding
を使用できます。
1つの方法は、これらのプロトコルのいずれかでオブジェクト(または構造体)をデコードし、そのプロパティをCore Dataのドキュメントごとに作成した新しいNSManagedObject
インスタンスに転送することです。
別の方法(非常に一般的)は、管理対象オブジェクトのプロパティに格納する非標準オブジェクトにのみプロトコルの1つを使用することです。 「非標準」とは、モデルで指定されているCore Dataの標準属性タイプに準拠していないものを意味します。たとえば、NSColor
は、CDがサポートする基本的な属性タイプの1つではないため、Managed Objectプロパティとして直接保存できません。代わりに、NSKeyedArchiver
を使用して色をNSData
インスタンスにシリアル化し、それを管理オブジェクトのDataプロパティとして保存できます。 NSKeyedUnarchiver
でこのプロセスを逆にします。これは単純化されており、コアデータを使用してこれを実行するより良い方法があります( 一時的な属性 を参照)が、それは私のポイントを示しています。
おそらくEncodable
(Codable
を構成する2つのプロトコルの1つ-他の名前を推測できますか?)を採用して、共有するためにManaged Objectインスタンスを直接JSONに変換することもできますが、 コーディングキーの指定 および独自のカスタムencode
実装。カスタムコーディングキーを使用してコンパイラによって自動合成されないためです。この場合、含めるonlyキー(プロパティ)のみを指定します。
お役に立てれば。
Casademoraのソリューションに従って、
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
あるべき
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
。
これにより、Xcodeが配列スライスの問題として誤って認識するエラーを防ぎます。
編集:暗黙的にアンラップされたオプションを使用して、使用されるたびにアンラップ.context
を強制する必要を取り除きます。
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
を使用したいという私の問題を解決しますが、すべてのクラス/プロパティを手動で作成することなく、コアデータとの関係と永続性も管理します。