導入の代わりに
Core Dataは、モデルのグラフを保存および管理するための強力で柔軟なフレームワークであり、iOS開発者の武器庫に取って代わるものです。 確かに、少なくともこのフレームワークについて何度も耳にしたことがあります。何らかの理由でまだ使用していない場合は、それを始めましょう。
むき出しの理論は通常非常に退屈で吸収されにくいため、実用的な例を使用してコアデータを操作し、アプリケーションを作成することを検討します。 私の意見では、「To-doリスト」などのコアデータを使用する一般的な例は、1つのエンティティのみを使用し、相互接続を使用しないため、あまり適切ではありません。 この記事では、複数のエンティティとそれらの間の関係を使用するアプリケーションを開発します。
読者はiOS開発の基本に精通していることを前提としています。ストーリーボードを知っており、MVCを理解し、基本的なコントロールの使用方法を知っています。 私自身は最近iOSに切り替えたので、記事に誤り、不正確さ、またはベストプラクティスを無視している可能性があります。 Xcode 7.3.1とiOS 9.3.2を使用しますが、他のバージョンでもすべて機能するはずです。
コアデータの概要
前述のように、Core Dataはデータモデルのオブジェクトグラフを保存および管理するためのフレームワークです。 もちろん、Core Dataを使用せずにデータを管理および保存できますが、このフレームワークを使用すると、はるかに快適で便利になります。
私の意見では、主要なコンポーネントと、コアデータが全体としてどのように機能するかを理解することが重要です。 つまり、学習曲線は、私がそう言うかもしれない場合、平均をわずかに上回るエントリーのしきい値を想定しています。 常に使用されるコアデータの主要なコンポーネントは次のとおりです。
- 管理対象モデル (管理対象モデル)-実際、これは(MVCパラダイムの)モデルであり、すべてのエンティティ、その属性、および関係が含まれています。
- 管理対象オブジェクトコンテキスト -モデルオブジェクトのコレクションを管理するために使用されます(一般的な場合、いくつかのコンテキストがあります)。
- 永続ストアコーディネーター -データウェアハウスとこのデータが使用されるコンテキストとの間の仲介者であり、データの保存とキャッシュを担当します。
もちろん、コアデータはこれらのコンポーネントだけに限定されません(以下で他のいくつかを検討します)が、これら3つはフレームワークの基礎を形成し、その目的と動作原理を理解することは非常に重要です。
例でコアデータの説明を続けましょう。
シングルビューアプリケーションテンプレートに基づいて新しいプロジェクトを作成し、新しいプロジェクトのオプションページで「コアデータを使用」チェックボックスを選択します。

このチェックボックスがオンの場合、Xcodeは空のデータモデルとプロジェクトにCore Dataを操作するための一定量のプログラムコードを追加します。 もちろん、既存のプロジェクトで既にコアデータの使用を開始できます。この場合、データモデルを自分で作成し、適切なプログラムコードを記述する必要があります。
デフォルトでは、XcodeはCore Dataを操作するためのコードをアプリケーションデリゲートクラス(
AppDelegate.swift
)に追加します。 それをより詳細に見てみましょう、それはコメントで始まります:
// MARK: - Core Data stack
4つの変数があり、それらはすべてクロージャーで初期化されます。 ただし、これらの最初の
applicationDocumentsDirectory
データを格納するためのディレクトリを返す単なるヘルパーメソッドです。 デフォルトでは、これは
Document Directory
、変更できますが、実際に必要になることはほとんどありません。 実装は単純であり、理解するのが難しくないはずです。
lazy var applicationDocumentsDirectory: NSURL = { let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) return urls[urls.count-1] }()
次の定義、
managedObjectModel
コアデータに最も関連しているため
managedObjectModel
より興味深いものです。
lazy var managedObjectModel: NSManagedObjectModel = { let modelURL = NSBundle.mainBundle().URLForResource("core_data_habrahabr_swift", withExtension: "momd")! return NSManagedObjectModel(contentsOfURL: modelURL)! }()
プログラムコードのロジックは簡単です。アプリケーションアセンブリから
momd
拡張子を持つ特定のファイルを取得し、それに基づいてオブジェクトデータモデルを作成します。 これがどのような種類のファイルであるかを調べることは残っています。 Projectナビゲーターのファイルを見ると、
xdatamodel
拡張子のファイルがあります-これはコアデータデータモデルです(これを操作する方法については後で説明します)。

さらに進んでください-persistentStoreCoordinatorは最も膨大な定義ですが、その恐ろしい外観にもかかわらず、怖がってはいけません-ほとんどのコードは例外によって処理されます:
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite") var failureReason = "There was an error creating or loading the application's saved data." do { try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil) } catch { var dict = [String: AnyObject]() dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" dict[NSLocalizedFailureReasonErrorKey] = failureReason dict[NSUnderlyingErrorKey] = error as NSError let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict) NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)") abort() } return coordinator }()
ここでは、オブジェクト駆動型モデルに基づいて、永続的なストレージコーディネーターが作成されます。 次に、データを保存する場所を決定します。 そして結論として、ストレージ自体(
coordinator.addPersistentStoreWithType
)を接続し、ストレージタイプとその場所をパラメーターとして対応するメソッドに渡します。 デフォルトはSQLiteです。 他の2つのパラメーターでは追加のパラメーターとオプションを渡すことができますが、この段階ではこれは必要ないため、
nil
渡すだけです。
最後の定義
managedObjectContext
問題はないと確信しています:
lazy var managedObjectContext: NSManagedObjectContext = { let coordinator = self.persistentStoreCoordinator var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType) managedObjectContext.persistentStoreCoordinator = coordinator return managedObjectContext }()
ここで、管理対象オブジェクトの新しいコンテキストを作成し、パーマネントストレージコーディネーターへのリンクを割り当てます。このリンクを使用して、必要なデータを読み書きします。 注目すべき詳細は、
NSManagedObjectContext
コンストラクターへの引数です。 一般的に、異なるスレッドで実行される複数の作業コンテキストが存在する可能性があります(たとえば、1つはインタラクティブな作業用、もう1つはバックグラウンドデータのロード用)。
MainQueueConcurrencyType
を引数として渡し、このコンテキストをメインスレッドで作成する必要があることを示します。
コンテキストを維持するための便利なヘルパー関数もここにあります。 その意味は明らかです。データは、実際に変更された場合にのみ記録されます。
func saveContext () { if managedObjectContext.hasChanges { do { try managedObjectContext.save() } catch { let nserror = error as NSError NSLog("Unresolved error \(nserror), \(nserror.userInfo)") abort() } } }
ここで注意することが重要です: データのすべての作業 (作成、変更、削除)は、 常に任意のコンテキスト内で行われます 。 ストレージへの実際の書き込みは、コンテキスト保存関数が明示的に呼び出されたときにのみ実行されます。
データモデルの作成
データモデルを作成するには、組み込みのエディターが使用されます。 新しいプロジェクトを作成するときに「コアデータを使用」をチェックしたため、空のデータモデルが既にあり、自動的にXcodeが作成されます。 それを開いて、アプリケーションのデータモデルを作成しましょう。

特定のサービスを実行するために、取引先からの注文を追跡するアプリケーションを作成します。 このアプリケーションはそれほど複雑ではありませんが、密接に関連するいくつかの異なるエンティティがあります。 これにより、コアデータを操作するためのさまざまな側面と手法がわかります。 そのため、 「Customers」と「Services」の 2つのディレクトリと、複数のサービスが存在する可能性のある1つのドキュメント「Order」を作成します。
叙情的な余談
「ディレクトリ」と「ドキュメント」という用語は、「1C:エンタープライズ」という用語から取ったものです。コアシステムを思い出させるのはこのシステムだからです。 エンティティ(ディレクトリ/ドキュメント)を構築するための同様のロジック、同様の属性パラメーター、データの読み取り/書き込み操作のカプセル化、キャッシュなど。 「1C:Enterprise」は、コアデータに関連する次のレベルのデータ抽象化です。
さて、ブラックジャックと通常のデザインで「1C:Enterprise」 を書きましょう!
さて、
ディレクトリの作成
顧客から始めましょう。 データモデルエディターで、新しいエンティティ(下部に「エンティティを追加」という署名が付いたボタン)を追加し、
«Customer»
という名前を付けます。 このエンティティは、顧客(1つ)を擬人化します。 エンティティには、属性、関係、および取得したプロパティ(フェッチプロパティ)を含めることができます。 少し簡略化すると、可能な値のタイプにおける属性と関係の違いは次のとおりです。属性は単純なデータ型(文字列、数値、日付など)のみをサポートし、関係は別のエンティティへの参照です分)。 フェッチプロパティは、計算されたプロパティに類似しています。つまり、値は、事前定義されたクエリに基づいて動的に計算(およびキャッシュ)されます。
DBMSを使用して、次の類似性を引き出すことができます。
- データモデル-データベーススキーマ
- エンティティ-データベーステーブル
- 属性と関係-テーブルフィールド
エンティティ
«Customer»
には、 「Name」と「Extras 」の 2つの属性があります。 情報”(
info
) 。 それらを追加して、
String
値型に設定してみましょう。 データモデルエディタでは、オブジェクトの命名に特定の要件があることに注意してください。エンティティの名前は必ず大文字で始まり、属性の名前と小さな名前との関係が必要です。

次の重要な部分はデータモデルインスペクターで、データモデルエディターの右側に表示されます。 これを使用して、エンティティ、エンティティ属性(トートロジーについてはすみません)、関係、およびその他のオブジェクトのさまざまな属性とパラメーターを設定できます。 たとえば、エンティティを抽象としてマークしたり、親エンティティを指定したりすることができます(原則はOOP全体と同じです)。
エンティティ属性の場合、使用可能なパラメーターのリストは属性の種類によって異なります。 たとえば、数値の場合は下限および/または上限を指定でき、日付の場合は有効な範囲を指定できます。 ほとんどの値タイプにデフォルト値を設定することもできます。
重要な属性プロパティはOptionalです。 その意味は、Swiftプログラムコードとまったく同じです。属性がOptionalとしてマークされている場合、その値は存在しない可能性があり、逆も同様です-そのようなマーキングがない場合、エンティティレコードは不可能になります。 デフォルトでは、すべての属性はオプションとしてマークされています。 私たちの場合、
name
ない顧客には実用的な意味がないため、
name
属性はオプションであってはなりません(Optionalフラグはオフにする必要があります)。
この時点で、
Customer
エンティティの作成は完了したと見なすことができます。 次のエンティティ-Servicesを作成して設定しましょう。 新しいエンティティの作成-
Services
を作成し、
name
(サービスの名前)と
info
(追加情報)の2つの属性を追加します。 両方の場合のデータ型は
String
で、
name
属性はオプションであってはなりません。 一般に、すべては前のエンティティと同じであり、ここで問題が発生することはありません。

注文ドキュメントの作成
ドキュメント「注文」に渡します-ここではすべてが少し複雑です。 1つのドキュメントに複数の異なるサービスを含めることができ、各サービスには独自の量があるため、ドキュメントは2つのエンティティによって表示されます。
- ドキュメントの「ヘッダー」。ドキュメントの日付、顧客、および表形式部分へのリンクが含まれます。
- サービスに含まれるドキュメントの表部分の行とそのコスト、およびドキュメントのヘッダーへのリンク。
最後の段落の内容がわからなくても心配しないでください。 これをすべてデータモデルエディターで一緒に行い、最後にモデルのグラフィカルな表現を確認します。その後、すべてが適切に配置されます。
ドキュメントの「ヘッダー」から始めましょう-新しいエンティティ
«Order»
を作成し、3つの属性を追加します(ここにあるものはすべて、以前のエンティティの作成からすでにおなじみです)。
-
date
-ドキュメントの日付、タイプDate
、オプションではない -
paid
-支払いのサイン、タイプBoolean
、オプションではなく、デフォルト値はNO
-
Boolean
注文完了のサイン、タイプBoolean
、オプションではなく、デフォルト値はNO
次に、 関係に進みます。
«customer»
という名前の新しい関係を追加し、その
Destination
を
Customer
設定します。 多少のストレッチはありますが、類推を続けると、
Order
テーブルに
«Customer»
タイプの新しい列を追加したと言えます。

デフォルトの関係もオプションであることに注意してください。 さらに、次の非常に重要なプロパティがAttributes Inspectorに存在します。これについて詳しく説明します。
- 種類
- ルールの削除( ルールの削除)
- 逆 (フィードバック)
種類
データベースを使用したことがある場合、この概念はおそらくおなじみでしょう。 ここでは、 To OneとTo Manyの2つのオプションを選択できます。 To One-私たちの注文は1人の特定の顧客、 To Many-複数の顧客に関連付けられていることを意味します。 この場合、デフォルト値-To Oneのままにする必要があります。
ルールの削除(ルールの削除)
非常に重要なプロパティです。ここでは、何らかの理由でこの接続が削除された時点で、エンティティの可能な動作の1つを選択する必要があります。 次のオプションが可能です。
- アクションなし -Core Dataは、削除の通知を含むアクションを実行しません 。 エンティティは削除がなかったと「考える」でしょう。 この場合、必要なアプリケーションの動作を個別に実装する必要があります。 あなたはこれを使いたくないでしょう。
- Nullify-接続が削除されると、その値は
nil
に設定されます。 デフォルトでは、最も一般的なオプションが使用されます。 - カスケード (カスケード削除)-接続を削除すると、そのリンクにリンクしているすべての顧客が自動的に削除されます(明らかに私たちの場合ではありません)
- 拒否 -前のルールとは逆のルールです。その本質は、少なくとも1つのリンクがある間はオブジェクトを削除できないということです。 たとえば、このようなアプローチは、1C:Enterpriseのすべてのオブジェクトに適用されます。
実際、選択する動作はプログラムロジックによってのみ決定されます。 これを気にせず、デフォルト値であるNullifyのままにしておきます。
逆(フィードバック)
「注文」と「顧客」の関係を追加しましたが、「顧客」は参加する「注文」について何も知りません。 警告もこのことを警告しています。

これを修正するには、エンティティ「顧客」との逆の関係を作成し、それをフィードバックとして指定する必要があります。 Core Dataの公式ドキュメントでは、常に逆接続を行うことを強くお勧めしていることに注意する必要があります。 厳密に言えば、これはできません(結局、これはエラーではなく警告です)が、これを行う理由と理由を明確に理解する必要があります。
これを修正し、
orders
という名前の
Customer
エンティティの新しいリレーションシップを作成し、
Destination = Order
を選択し、フィードバックとして、先ほど作成した
customer
リレーションシップを指定し
customer
。 別のポイント-1人の顧客が一般的なケースでは多くのドキュメントを持っている可能性がある
To Many
、通信のタイプを
To Many
ます。

«Order»
エンティティに戻ると、フィードバックが既に
orders
自動的に設定されていることがわかります。
次に、ドキュメントの表部分を作成しましょう。
«RowOfOrder»
という名前の新しいエンティティを追加します。 Floatタイプの
«sum»
(「サービスの合計」)(これを行う方法は既に知っています。詳細は説明しません)と2つの関係( 「Service」と「Order」 )の1つの属性があります。 Orderから始めましょう
Destination
という名前と
Destination
と等しい新しい関係を追加します。 文書行は1つの文書にのみ属することができる
To One
、
Type
は
To One
なければなりません。 さて、ドキュメントを削除することにした場合、
Delete Rule
Cascade
があるため、その行も削除する必要があります。

次に、 Orderエンティティに戻ってフィードバックを作成します。
rowsOfOrder (Destination = RowOfOrder, Inverse = order)
という名前の新しいリンクを追加します。 リンクタイプを
To Many
に変更することを忘れないでください(1つのドキュメントに複数の行がある場合があるため)。

Serviceエンティティーとの関係のみをRowOfOrderエンティティーに追加します。 上記のすべてを考えると、これはすべて同じシナリオで複雑になるべきではありません。
«RowOfOrder»
エンティティのネーム
service (Destination = Service)
との新しい関係を追加し、残りはデフォルトのままにします。 次に、
Service
エンティティに対して、新しい関係
«rowsOfOrders» (Destination = rowOfOrder, Inverse = service)
を追加し、接続タイプを
To Many
設定します。

重要なお知らせ! データモデルを作成した後、変更することはできません。CoreDataアプリケーションを最初に起動すると、データモデルに従ってリポジトリが作成され、その後、ストレージモデルのコンプライアンスがチェックされます。 何らかの理由でストレージ構造がデータモデルと一致しない場合、重大なランタイムエラーが発生します(つまり、アプリケーションは動作不能になります)。 しかし、データモデルを変更する必要がある場合-コアデータ移行メカニズムを使用する必要がある場合、これは複雑さを増す別個のトピックであり、この記事のフレームワークでは考慮しません。 別のオプションがあります-デバイス(またはエミュレーター)からアプリケーションを削除するだけで、アプリケーションが起動すると、Core Dataは新しい構造で新しいストレージを作成します。 明らかに、このメソッドはアプリケーション開発の段階でのみ関連します。
この記事を締めくくるために、そのグラフィカル表現を見てみましょう;これを行うには 、データモデルエディター(下にある)のエディタースタイルを グラフの位置に切り替えます。

属性で作成したエンティティと、そのすべての関係がグラフィック構造の形で表示されます。 端に通常の矢印が付いた線は、 1つの接続を意味し、二重矢印-To Manyを意味します。 グラフィックビューは、体積モデルをナビゲートするのに役立ちます。
最初の部分は終わりです。 次の記事では多くのコードがあります。オブジェクト自体を作成し、それらをリンクし、
NSEntityDescription
と
NSManagedObject
、コアデータの使いやすさを大幅に向上させるヘルパークラスを作成します。
このプロジェクトはgithubにあります