月にdonnywalsによって公開されました5, 2020
Xcodeがコアデータモデルファイルに基づいてNSManagedObject
クラスを生成するとき、管理オブジェクトのプロパティのほとんどはオプションであることに気づいた モデルエディタでそれらを必須にした場合でも、Xcodeはほとんどのプロパティがオプションである管理オブジェクトを生成します。
この記事では、この現象とそれがなぜ起こるのかを探ります。
生成されたNSManagedObjectサブクラスの探索
コアデータモデルにXcodeの自動コード生成を使用するプロジェクトをビルドすると、プロジェクトのビルド時にNSManagedObject
サブクラスが生成されます。 これらのクラスはプロジェクトの派生データフォルダに記述されているため、直接変更するべきではありません。 Xcodeによって生成されるモデルには、モデルエディタでプロパティをオプションにしたかどうかにかかわらず、エンティティに追加した一部のプロパテ
これは最初は奇妙に聞こえるかもしれませんが、実際にはそれほど奇妙ではありません。 次のNSManagedObject
サブクラスを見てみましょう:
extension ToDoItem { @nonobjc public class func fetchRequest() -> NSFetchRequest<ToDoItem> { return NSFetchRequest<ToDoItem>(entityName: "ToDoItem") } @NSManaged public var completed: Bool @NSManaged public var label: String?}
myToDoItem
の2つのプロパティのうちの1つは、モデルエディタで両方とも必要であってもオプションです。 このToDoItem
のインスタンスを作成するときは、次のコードを使用します:
let item = ToDoItem(context: managedObjectContext)
管理対象オブジェクトの初期化子は、管理対象オブジェクトコンテキストを取ります。 これは、ToDoItem
の初期化中に管理プロパティに値を割り当てないことを意味します。 label
プロパティとcompleted
プロパティの両方の値を印刷すると、結果が得られ、興味深い結果が得られます:
print(item.label) // nilprint(item.completed) // false
label
は予想どおりnil
ですが、Core Dataはデフォルト値false
をcompleted
プロパティに割り当てました。 さらに一歩進んで、次のコードを見てみましょう:
let item = ToDoItem(context: managedObjectContext)item.label = "Hello, item"print(item.label) // "Hello, item"print(item.completed) // falsedo { try managedObjectContext.save()} catch { print(error)}
このコードを実行すると、次の出力が生成されることがわかります:
Optional("Hello, item")falseError Domain=NSCocoaErrorDomain Code=1570 "completed is a required value." UserInfo={NSValidationErrorObject=<CoreDataExample.ToDoItem: 0x60000131c910> (entity: ToDoItem; id: 0x600003079900 <x-coredata:///ToDoItem/t1FABF4F1-0EF4-4CE8-863C-A815AA5C42FF2>; data: { completed = nil; label = "Hello, item";}), NSValidationErrorKey=completed, NSLocalizedDescription=completed is a required value.}
このエラーは、completed
が設定されていないことを意味するcompleted is a required value.
を明確に示し、エラーメッセージと一緒に表示される印刷された管理オブジェクトもcompleted
がnil
であるこ したがって、completed
には何らかの種類のデフォルト値が存在しますが、明示的に割り当てられるまでは非nil
とはみなされません。
何が起こっているのかを理解するために、completed
に値を割り当て、item
の印刷された説明をもう一度見てみることができます:
let item = ToDoItem(context: managedObjectContext)item.label = "Hello, item"print(item.completed)print(item)
このコードは、次の出力を生成します:
false<CoreDataExample.ToDoItem: 0x6000038749b0> (entity: ToDoItem; id: 0x600001b576e0 <x-coredata:///ToDoItem/tD27C9C9D-A676-4280-9D7C-A1E154B2AD752>; data: { completed = 0; label = "Hello, item";})
これはかなり興味深いですね。
completed
プロパティはBool
として定義されていますが、数値として出力されます。 この理由は、基礎となるSQLiteストアで見つけることができます。 Core Data storeのSQLiteファイルを調べる最も簡単な方法は、アプリの起動引数として-com.apple.CoreData.SQLDebug 1
を渡し、Core DataがSQLiteデータベースブラウザなどのSQLiteエクスプローラで接続するSQLite
ヒント:
コアデータ起動引数の詳細については、この記事を参照してください。
ZTODOITEM
のスキーマ定義を見ると、ZCOMPLETED
の型としてINTEGER
が使用されていることがわかります。 これは、completed
プロパティが基になるSQLiteストアに整数として格納されていることを意味します。 completed
がINTEGER
として格納される理由は簡単です。 SQLiteにはBOOLEAN
型がなく、代わりにfalse
を表すためにINTEGER
値0を使用し、代わりにtrue
を表すために1を使用します。マネージオブジェクトインスタンスを印刷するときに表示されるdata
は、completed
プロパティの値ではなく、SQLiteストアに書き込まれるcompleted
の値です。
この節から学ぶべきことは2つあります。
まず、定義されたコアデータモデルと生成された管理対象オブジェクトのオプションの間に不一致があることがわかりました。 非オプションのString
は、生成されたモデルではオプションのString
として表され、非オプションのBool
は、生成されたモデルではオプションのBool
として表されます。
次に、マネージオブジェクトモデルでの値の表現方法と、基になるSQLiteストアでの値の表現方法に違いがあることを学びました。 マネージオブジェクトインスタンスを基になるストレージに書き込むために使用される値を確認するには、マネージオブジェクトを印刷し、印刷出力でdata
ここでの主な教訓は、モデルエディタのコアデータモデルとマネージオブジェクトサブクラスがデータを同じように表現しないことです。 コアデータモデルでのOptionalは、マネージオブジェクトサブクラスでは常にoptionalを意味するとは限らず、その逆もあります。 コアデータモデル内の省略可能でない値は、マネージオブジェクトサブクラス内の省略可能な値として表される場合があります。 コアデータは、永続ストアに書き込むときに管理対象オブジェクトをその管理対象オブジェクトモデルに対して検証し、検証エラーが発生した場合はエ
では、なぜこの不一致が存在するのですか? マネージオブジェクトモデルとマネージオブジェクトサブクラスに直接マッピングがある場合は、はるかに簡単ではないでしょうか?
マネージオブジェクトとコアデータモデルの不一致の理解
マネージオブジェクトとモデルエディタで定義したモデルの間に不一致がある理由の大部分は、Core DataのObjective-Cのルーツから来ています。Objective-CはOptional
をまったく扱っていないので、モデル定義からSwiftコードへの適切なマッピングは必ずしもありません。 多くの場合、マッピングの動作方法はやや恣意的なようです。 たとえば、Optional<String>
とOptional<Bool>
は、Optional
がObjective-Cに存在しないという単純な理由で、Objective-cの型として表すことはできません。 残念ながら、Optional<Bool>
は、@NSManaged
プロパティをBool?
として定義しようとするとXcodeが通知するため、Objective-Cの何かに自動的にマップすることはできません。Objective-Cで作業したことがない場合、Optional
という概念がないことは非常に奇妙に思えるかもしれません。 Swiftの前にCore Dataでオプションのプロパティをどのように使用しましたか? そして、Objective-Cで何かがnil
になるとどうなりますか?Objective-Cでは、期待していない場合でも、値がnil
であることは完全に問題ありません。 コアデータはObjective-Cにルーツを持っているので、このレガシーのいくつかは、時には理想的ではない方法で生成されたSwiftクラスに引き継がれます。
ここで最も重要なのは、Objective-Cの仕組みやXcodeがコードを正確に生成する方法ではありません。 代わりに、コアデータモデル定義の型と構成が、(生成された)管理対象オブジェクトのサブクラスの型と一致しないことを覚えておいてください。
まとめ
今週の記事では、マネージオブジェクトサブクラスとコアデータモデル定義が必ずしも期待どおりに並んでいるとは限らないことにつ モデルエディターのオプション以外のプロパティは、生成されたマネージオブジェクトサブクラスでオプションとして終わることがあり、それ以外の場合は、デフォルト値を自分で割り当てていなくても、デフォルト値を持つオプション以外のプロパティとして終わることがあります。
また、デフォルト値がマネージオブジェクトインスタンスに存在する場合、Core Data modelエディターで明示的にデフォルト値を定義しない限り、マネージオブジェク
これは確かに混乱して不幸ですが、Core Dataは管理オブジェクトの保存中にスローされるエラーの何が間違っているかを伝えるのにかなり優れています。 また、コアデータが格納しようとする値を検査するには、マネージオブジェクトインスタンスを印刷し、そのdata
属性を検査します。
個人的なメモとして、今週の記事で説明した動作は、コアデータの将来のアップデートで対処され、マネージオブジェクトサブクラスがモデルエディタで定義されているコアデータモデルに近い、おそらく直接マッピングされている場合に、より迅速にフレンドリーになることを願っています。 しかし、それまでは、モデルエディタとマネージオブジェクトサブクラスが同じ方法でモデルを表すのではなく、これがコアデータのObjective-Cルートに少なくとも
この投稿に関する質問、修正、フィードバックがあればTwitterでお知らせください。 この記事は、私が取り組んでいるコアデータに関する本のためにやっている研究、探査、準備の一部です。 この本についての更新については、Twitterで私に従うことを確認してください。 私は現在、2020年の終わり頃に本をリリースする予定です。
私の毎週のニュースレターを最新の状態に滞在
Practical Combine
Combineについて知っておく必要があるすべてを学び、私の新しい本Practical Combineであなたのプロジェクトでそれをどのように使用できるかを学びます。 あなたは13章、遊び場とあなたができるだけ早くCombineを起動して実行するのを助けるためにサンプルプロジェクトの一握りを取得します。
この本はちょうど2 29.99のためのデジタルダウンロードとして利用可能です!