Swiftで遊ぼう! - 630 - Zoomania
- Swiftで遊ぼう!の前書き-> Life-LOG OtherSide
- Swift2.1 & Xcode7.1対応の日本語版アップルチュートリアル!
- Table View実装チュートリアルをXcode7.2.1で解説
- Core Data シンプルチュートリアル
- 私の本業、オフィシャルなブログ-> Life-LOG
CoreDataに関してかなり理解がすすみました。更に複雑なチュートリアルをこなすことでNSFetchedResultsControllerの扱いになれてしまおうと思います。
チュートリアルの名前は、「Zoomania」です! 私の作ったオリジナルチュートリアルではなく完全にパクりです。オリジナルは英語なんで日本語で紹介すると分からないでしょう。出典がバレたらちゃんと紹介します(チュートリアが完成しても)。
今回扱うデーターベースのエンティティ(テーブル)は複数あって関係性(リレーションシップ)も複数設定されています。実はリレーションシップが組み込まれたデーターベースをどのように扱ったらいいのかまだ分かってないんです(^_^;) 今回このチュートリアルをこなすことで完全に理解しようと思っています。
いつものようにXcodeプロジェクトを作ります。「Use CoreData」のチェックマークは忘れないようにします。
プロジェクト・ナビゲータに「strong>Zoomania.xcdatamodeld」があるので選択してエンティティとアトリビュートを設定します。ダイアグラムで関係をみると以下のようになります。
データーベース開発時に、データ無しでデーターベースを設計すると、ちゃんと動いているのかどうか確認しづらいため、マニュアルでちまちまデータを入力して確認するという作業を普通とります。このチュートリアルでは、データーベース開発をもう少し楽にするために自動サンプルデータシード(Seed)クラスの設計をして自動的にサンプルデータをシードします。
既に説明していますが、Use CoreDataにチェックマークを入れたところで、CoreDataを使ってSQLiteというデーターベースの利用ができるようになるんです。しかし、アプリケーションを立ち上げただけではデーターベースが作成されません。CoreDataを使って入力データを永続保存させることで、「Documents」ディレクトリに「Zoomania.sqlite」ができるんです。データーベースを新規に作った時は表示させるデータが無いからです。サンプルデータが入った「Zoomania.sqlite」ファイルをコードで作ってしまおうというのが今日の趣旨です。
まずこのプロジェクトにNew Fileを作って、Swift Fileを選びます。ファイル名を「DataHelper」にします。
Foundationだけインポートされたファイルが用意されます。
当然のようにCoreDataをインポートします。
そしてSQLiteに保存するために必要なインスタンスを宣言します。それはNSManagedObjectContextです。このクラスをどこでインスタンス化させるか考える必要があります。ViewControllerクラス内? いえ違います。NSManagedObjectStackがインスタンスされる場所です。それは、AppDelegateです。ということは、DataHelperクラスは、NSManagedObjectContextインスタンで初期化するクラスをコードするということで、最初はこんな始まりになります。
import Foundation import CoreData public class DataHelper { let context: NSManagedObjectContext // 更に続く...
NSManagedObjectContext型のプロパティを宣言したので、初期化ステップを使ってインスタンス化するイニシャライザーを用意する必要があります。
// ... init(context: NSManagedObjectContext) { self.context = context } // ...
これでNSManagedObjectContextプロパティが起動するので、これを使ってデータをシードするステップを書いていきます。まず最初のエンティティ「Zoo」のシードをするメソッドを用意します。エンティティ「Zoo」は2つのアトリビュート「name」と「location」を持っていて1つのリレーションシップ「animals」を持っているのですが、ここでは2つのアトリビュートだけシードします。リレーションシップはリレーションシップの集中する「Animal」でさせています。
// ... private func seedZoos() { let zoos = [ (name: "Tobe Zoo", location: "Matsuyama City"), (name: "Tokushima Zoo", location: "Tokushima City"), (name: "Noichi Zoo", location: "Kouchi City") ] for zoo in zoos { let newZoo = NSEntityDescription.insertNewObjectForEntityForName("Zoo", inManagedObjectContext: context) as! Zoo newZoo.name = zoo.name newZoo.location = zoo.location } do { try context.save() } catch let error as NSError { print("error is \(error)") } } // ...
「Zoo」と同様に「Classification」をシードさせるメソッドも用意します。保持するアトリビュートが3つなのでアレー型で3つの値をタプルでシードします。リレーションシップはZooと同様でAnimalで与えます。
private func seedClassifications() { let classifications = [ (scientificClassification: "Mammalia", order: "Loxodonta", family: "Stegotetrabelodon"), (scientificClassification: "Mammalia", order: "Artiodactyl", family: "Hippopotamidae"), (scientificClassification: "Mammalia", order: "Cetartiodactyla", family: "Giraffidae") ] for classification in classifications { let newClassification = NSEntityDescription.insertNewObjectForEntityForName("Classification", inManagedObjectContext: context) as! Classification newClassification.scientificClassification = classification.scientificClassification newClassification.family = classification.family newClassification.order = classification.order } do { try context.save() } catch let error as NSError { print("error is \(error)") } }
そして最も複雑なアトリビュート「Animal」をシードするメソッドを用意します。この中で私の知らないことが1つ。アレー型が持っている標準メソッド「filter」でした。こういう標準メソッドはプログラマーにとって基本的なことなんでしょうが、2年前に勉強を始めた私にとって標準メソッドでさえ知らないことばかりです。明日少しまとめます。
private func seedAnimals() { let classificationFetchRequest = NSFetchRequest(entityName: "Classification") let allClassifications = try! context.executeFetchRequest(classificationFetchRequest) as! [Classification] let elephant = allClassifications.filter({(c: Classification) -> Bool in return c.family == "Stegotetrabelodon" }).first let hippopotamus = allClassifications.filter({(c: Classification) -> Bool in return c.family == "Hippopotamidae" }).first let giraffe = allClassifications.filter({(c: Classification) -> Bool in return c.family == "Giraffidae" }).first let zooFetchRequest = NSFetchRequest(entityName: "Zoo") let allZoos = try! context.executeFetchRequest(zooFetchRequest) as! [Zoo] let tobeZoo = allZoos.filter({ (z: Zoo) -> Bool in return z.name == "Tobe Zoo" }).first let tokushimaZoo = allZoos.filter({ (z: Zoo) -> Bool in return z.name == "Tokushima Zoo" }).first let noichiZoo = allZoos.filter({ (z: Zoo) -> Bool in return z.name == "Noichi Zoo" }).first let animals = [ (commonName: "Grape eating elephant", habitat: "field Mamals Exhibit", classification: elephant!, zoos: NSSet(array: [tobeZoo!, tokushimaZoo!, noichiZoo!])), (commonName: "American Hippo", habitat: "Weltand Mamals Exhibit", classification: hippopotamus!, zoos: NSSet(array: [tobeZoo!, noichiZoo!])), (commonName: "Asian Hippo", habitat: "Weltand Mamals Exhibit", classification: hippopotamus!, zoos: NSSet(array: [tokushimaZoo!])), (commonName: "Short Neck Giraffe", habitat: "Aquatic Mamals Exhibit", classification: giraffe!, zoos: NSSet(array: [noichiZoo!])) ] for animal in animals { let newAnimal = NSEntityDescription.insertNewObjectForEntityForName("Animal", inManagedObjectContext: context) as! Animal newAnimal.commonName = animal.commonName newAnimal.habitat = animal.habitat newAnimal.classification = animal.classification newAnimal.zoos = animal.zoos } do { try context.save() } catch let error as NSError { print("error is \(error)") } }
これらがエンティティを個別にシードするメソッドなので、まとめてシードするメソッドを書きます。
public func seedDataStore() { seedZoos() seedClassifications() seedAnimals() }
これでデータのシードには問題がありません。しかし開発段階で重要なことはデータがちゃんと永続保存されているかどうか確認できることです、と言うわけで確認用のコンソールへの書き出すメソッドも用意します。それは次回にします。