Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 630 - Zoomania

CoreDataに関してかなり理解がすすみました。更に複雑なチュートリアルをこなすことでNSFetchedResultsControllerの扱いになれてしまおうと思います。

チュートリアルの名前は、「Zoomania」です! 私の作ったオリジナルチュートリアルではなく完全にパクりです。オリジナルは英語なんで日本語で紹介すると分からないでしょう。出典がバレたらちゃんと紹介します(チュートリアが完成しても)。

今回扱うデーターベースのエンティティ(テーブル)は複数あって関係性(リレーションシップ)も複数設定されています。実はリレーションシップが組み込まれたデーターベースをどのように扱ったらいいのかまだ分かってないんです(^_^;) 今回このチュートリアルをこなすことで完全に理解しようと思っています。

いつものようにXcodeプロジェクトを作ります。「Use CoreData」のチェックマークは忘れないようにします。

プロジェクト・ナビゲータに「strong>Zoomania.xcdatamodeld」があるので選択してエンティティとアトリビュートを設定します。ダイアグラムで関係をみると以下のようになります。

f:id:yataiblue:20160330152114j:plain

データーベース開発時に、データ無しでデーターベースを設計すると、ちゃんと動いているのかどうか確認しづらいため、マニュアルでちまちまデータを入力して確認するという作業を普通とります。このチュートリアルでは、データーベース開発をもう少し楽にするために自動サンプルデータシード(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()
}

これでデータのシードには問題がありません。しかし開発段階で重要なことはデータがちゃんと永続保存されているかどうか確認できることです、と言うわけで確認用のコンソールへの書き出すメソッドも用意します。それは次回にします。