【Swift】Core Dataの設定。エンティティに複合インデックスや一意制約を付与する。(Swift 2.1、XCode 7.2)
Entityの設定
本記事は、前回記事「Entityの設定」の続きである。⇒「前回記事」
Indexes
インデックスを作る属性を指定する。インデックスとは、データ検索を高速化するための索引のことである。
例えば下図のように、データが無秩序に並んでいる中から3を探す場合、全領域に3が存在する可能性があるので、すべてデータを確認する必要がある。これがインデックスが作成されていない状況。
下図のような少ない件数であれば問題ないが、これが何千、何万という件数になったらかなり時間がかかりそうなのは想像に易い。
上記のデータが下図のように数字の小さい順に並んでいた場合、検索データから後ろのデータは確認する必要がなくなる。これがインデックスを作成したイメージである。何千、何万という件数なら大幅に検索時間を短縮できる。
なお、これはインデックスを説明するためのイメージ図であり、実際にこのように並ぶわけではない。
インデックスには単一インデックスと複合インデックスがあるが、Indexesの設定項目は、主に複合インデックスを定義するのに使われる。単一インデックスは属性ごとの設定画面からも設定できる。
単一インデックスとは、1つの属性をキーにしてデータを検索するときに使われるインデックスである。例えば「name=’佐藤’」で検索するときは、属性nameのみ使われるので単一インデックスである。
複合インデックスとは、2つ以上の属性をキーにしてデータを検索するときに使われるインデックスである。例えば「name=’佐藤’ かつ age=20」で検索するときは、属性nameとageが使われるので複合インデックスである。
複合インデックスを定義してみよう。下図赤枠の「+」ボタンを押すと「comma,separated,propaties」の行が追加されるので、その行をクリックして値を変更可能にし、「name,age」を入力する。
1つ不思議な事象が発生した。
Indexesに「hoge,hoge」などの存在しない属性名を入力してもエラーが検出されずにシミュレーターが正常に起動してしまうという事象だ(Swift 2.1、XCode 7.2)。
その場合、Abstract Entityなどの他の設定項目の値を変更すると、それに反応してIndexesに入力した値が確定し、エラーが検出されるようになった。
インデックスはデータ検索を高速化させる利点がある一方、データが追加されるたびにインデックスの更新が実施されるので、データ追加が頻繁に行われるエンティティにインデックスをたくさん定義すると、データ追加時の処理時間が長くなってしまうので注意されたし。
Constraints
ここに追加した属性はデータが重複できなくなる(一意制約)。例えば、nameが「佐藤」のデータ2つを保存しようとすると、2つ目のデータを保存するタイミングでConflict(衝突)のエラーが発生する。
組み合わせた属性値を重複させたくない場合は属性名をカンマ区切りで入力する(例、name,age)。
実際にConflictを発生させてみよう。
下図赤枠の「+」ボタンを押すと「comma,separated,propaties」の行が追加されるので、その行をクリックして値を変更可能にし「name」を入力する。
ViewController.swiftを以下のコードに変更する。エラーが発生したら、エラーの内容をアラート表示するようにした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
// // ViewController.swift // import UIKit import CoreData class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var testLabel: UILabel! @IBOutlet weak var testTextField: UITextField! //管理オブジェクトコンテキスト var managedContext:NSManagedObjectContext! //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() do { //管理オブジェクトコンテキストを取得する。 let applicationDelegate = UIApplication.sharedApplication().delegate as! AppDelegate managedContext = applicationDelegate.managedObjectContext //管理オブジェクトコンテキストからPlayerエンティティを取得する。 let fetchRequest = NSFetchRequest(entityName: "Player") let result = try managedContext.executeFetchRequest(fetchRequest) as! [Player] //すべてのPlayerエンティティの名前をラベルに表示する。 for data in result { testLabel.text = testLabel.text! + "," + data.name! } //デリゲート先に自分を設定する。 testTextField.delegate = self } catch { //エラーメッセージをアラートで表示する。 let alert = UIAlertController(title:"エラー", message:String(error), preferredStyle:UIAlertControllerStyle.Alert) self.presentViewController(alert, animated:true, completion:nil) } } //Returnキー押下時の呼び出しメソッド func textFieldShouldReturn(textField:UITextField) -> Bool { do { //ラベルの値にテキストフィールドの値を追記する。 testLabel.text = testLabel.text! + "," + testTextField.text! //新しいPlayerエンティティを管理オブジェクトコンテキストに格納する。 let player = NSEntityDescription.insertNewObjectForEntityForName("Player", inManagedObjectContext: managedContext) as! Player //Playerエンティティの名前にテキストフィールドの値を設定する。 player.name = testTextField.text //管理オブジェクトコンテキストの中身を永続化する。 try managedContext.save() //キーボードをしまう self.view.endEditing(true) } catch { //エラーメッセージをアラートで表示する。 let alert = UIAlertController(title:"エラー", message:String(error), preferredStyle:UIAlertControllerStyle.Alert) self.presentViewController(alert, animated:true, completion:nil) } return true } } |
以下は実際のプレイ動画。
なお、Indexesの場合と同様に、Constraintsに値を設定した直後は設定が反映されていない。シミュレータを起動して同じ文字列を2回入力してもコンフリクトが発生しない場合は、Abstract Entityなどの他の項目の値をオンオフしてから起動してみるのを試してほしい。
エラーを発生させないで、コンフリクトが発生したデータをマージさせる場合は、以下のコードのように管理オブジェクトコンフリクトのマージポリシーを変更する。⇒「参考サイト」
マージが実施されるのは、管理オブジェクトコンテキストにオブジェクトを格納したときではなく、saveメソッドを呼んだタイミングで実施される。
1 2 3 |
//コンフリクトが発生した場合はマージする。 managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy |
変更を破棄する場合は以下のコードにする。
1 2 3 |
//コンフリクトが発生した場合は変更を破棄する。 managedContext.mergePolicy = NSRollbackMergePolicy |
User Info
エンティティの設定の下にUser Infoという設定項目がある。
User Infoとは、項目につける付加情報のことで、エンティティの設定の下にあるUser Infoは、選択しているエンティティにつける付加情報である。
実際にエンティティにUser Infoを追加し、ソースコードで値を取り出してみよう。
下図赤枠の「プロジェクト名.xcdatamodeld」を選択、黄緑枠のPlayerエンティティを選択、紫枠のデータモデルインスペクタボタンを押して設定画面を表示する。
水色枠の「+」ボタンを押してUser Infoの行を追加する。Keyに「testKey」、Valueに「スポーツ選手」を入力する。
ViewController.swfitの「Returnキー押下時の呼び出しメソッド」に以下のコードを追加する。
testKeyに紐づく値をUser Infoから取り出しラベルに設定している。
1 2 3 |
//ラベルにUser Infoの値を設定する。 testLabel.text = player.entity.userInfo!["testKey"] as? String |
以下は実際のプレイ動画