CoreData
model
Swift

【初心者向け】Core Dataの使い方と説明swift3.0

Core Data 勉強したので、簡単な説明とプロジェクト例を載せました!

Core Dataとは?

Core Dataとはモデルオブジェクトを扱うためのフレームワークです!Core Data発表されてからモデルレイヤーを作るのが楽になった(はず)。Core Dataはパーシスタンスを使っていて、これによりモデルオブジェクトの状態をディスクに維持している。

データの保存・取得は当然できるだけではなく、メモリの中のデータを直接扱えるところが特徴です。

Core DataはORM(Object-relational Mapper)ではないです。SQLiteをデフォルトで使っていますが、SQLiteラッパーではない。どちらかというとモデルをラップするフレームワークで、これがCore Dataの強みで、
Object Graph Management と呼ばれています。

メリット

第三者のデータベースではなく、Appleがサポートしているものなので、アップデートが楽!という点でもナイスです。

下図のように、xCode上でグラフィカルに簡単にモデルクラスが作れちゃうよ♪
Screen Shot 2017-07-25 at 7.21.07 PM.png

こんなことを説明します

  • Xcodeのモデルエディターを使ってModelを作る
  • Core Data に新しく情報を追加
  • Core Dataから情報をひっぱりだす。
  • テーブルビューを使って情報を見せる

図でわかるCore Dataクラス

Screen Shot 2017-07-26 at 12.40.46 PM.png

全てのコンポーネントがつながっているこの状態をCore Data Stackと呼ばれています。
このスタックは、Object Graph Management(左)とPersistance(パーシスタンス)(右)の2パートに分かれています。その二つを繋いでいるのがNSPersistantCoordinatorです。
パーシスタンスはモデルオブジェクトの状態を保存して、その状態をまた取得することができます。データファイルを取得するのに、NSPersistant StoreというのをNSPersistantから繋げなくてはいけなくて、そこを通じてデータと交信します。
Object Graph Managementでは複数のコンテキストをサポートしています。それぞれのコンテキストがManaged Objectを管理していて、それぞれの関係(relationship)が解っています。

Core Dataは複数のコンテキストをサポートしている、と言いましたが、
ほとんどの場合一つのファイルと一つのコンテキストしか使わないです。

Data ModelファイルをxCodeでを作ろう!

Core Dataを使うときは、まずData Modelファイルを作ります。
いつものようにNew File をして、Data Modelを選び、EntitiesやAttributesを下記のように追加してください。
Editor Styleを変えると、UMLダイアグラム的なビューで編集できるので、ここから編集するのもアリですね。
Screen Shot 2017-07-26 at 2.17.27 PM.png

図でも説明しましたが、データタイプがないときは、"transformable"で設定しておいて、取得時に本来のデータタイプに置き換えます。本来のデータタイプの右のエディターで設定しておきましょう。
Screen Shot 2017-07-26 at 2.35.40 PM.png

Manual/None vs. Class Definition vs. Category/Extention

Entityを選ぶと右側にManual/None, Class Definition Category/Extentionがあります。どれかを選ぶことによって、ファイルの数とデータの書き方がちょっと変わるのでこの2パターンを比較しましょう。
Screen Shot 2017-07-26 at 2.58.57 PM.png

Class Definition

これを選ぶと、裏でデータクラスを作ってくれるので、物理的なファイルを使いません、という宣言をしている感じになります。全く同じ名前のデータクラスを作ってしまうと、コンパイル時に文句を言われます。

このプロジェクト分かりやすかったです↓
モデルクラスファイルなしのプロジェクト例

Manual/None

これを選んで、Modelクラスファイルを生成すると、自分で作ったEntitiesとModelと連動させることができます。
生成ってなんやねん?
Editor > Create NSManaged Object Subclass...を選んで進んでいくと勝手に自分で作ったモデルファイルを元にモデルクラスを生成してくれます。
Screen Shot 2017-07-26 at 4.19.43 PM.png

それぞれのファイルの中身こんなです。

PhotoSaved+CoreDataClass.swift
import Foundation
import CoreData

@objc(PhotoSaved)
public class PhotoSaved: NSManagedObject {

//追加コードやメソッドがあればここに書く!!

}
PhotoSaved+CoreDataProperties.swift
import Foundation
import CoreData


extension PhotoSaved {

//このソースはタッチしないのがベスト

    @nonobjc public class func fetchRequest() -> NSFetchRequest<PhotoSaved> {
        return NSFetchRequest<PhotoSaved>(entityName: "PhotoSaved")
    }

    @NSManaged public var dateTaken: NSDate?
    @NSManaged public var photoID: String?
    @NSManaged public var remoteURL: NSURL?
    @NSManaged public var title: String?

}

物理的なファイルなくてもデータを取得できますが、あると追加コードが書けます。追加コードは1個目のPhotoSaved+CoreDataProperties.swiftに書いてください。
2個目のPhotoSaved+CoreDataProperties.swiftは中身をタッチしないのがベスト。Attributesの変更があれば、Photo.xcdatamodeldのファイルをいじってファイルを再生成してくだい。

Manual/Noneを使っているプロジェクト例

Category/Extention

これを選ぶと追加コードしか読みません!と宣言しています。Attributesなどは内部的に宣言しているので、追加でメソッドなど追加したいときだけにはこれを選びましょう。
上記のManual/Noneと同じようにファイルを生成しますが、
PhotoSaved+CoreDataProperties.swiftは消して、追加コードを書くPhotoSaved+CoreDataClass.swiftだけ残してください。

前置きが長かったですが、これからはコードを見ましょう。

使い方

ファイルとコードを自動生成しよう。

Screen Shot 2017-07-28 at 11.15.49 AM.png

下の①と④はxCodeが勝手に作ってくれるので、自分で書かなくても良いのですが...
語るネタがなくなるので、下に説明とコード載せておきました。

0. Core Dataをimportする

coredataを使うswiftファイルに追加してください。

import CoreData

1. NSPersistantContainerをinitする

AppDelegate.swift
  lazy var persistentContainer: NSPersistentContainer = {

    let container = NSPersistentContainer(name: "HitList")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
      if let error = error as NSError? {

        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    })
    return container
  }()

container.loadPersistentStoresメソッドで最初にデータベースにストアされいるファイルを読み込んでます。
最初の図を見るとNSPersistantCoordinatorを使うんじゃないの?って思うかもですが、Containerを使って、Coordinator, ManagedObject, PersistantStoresの取得ができるっぽいです。↓
Screen Shot 2017-07-26 at 1.42.31 PM.png

2. データの取得

先ほどのcontainerがAppDelegate.swiftに書かれている場合は、まずはappDelegateを定義します。

ViewController.swift
var fetchedArray: [NSManagedObject] = []

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
      return
    }

    let managedContext = appDelegate.persistentContainer.viewContext

    let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
    do {
      fetchedArray = try managedContext.fetch(fetchRequest)
    } catch let error as NSError {
      print("Could not fetch. \(error), \(error.userInfo)")
    }
  }

Contextを定義して、それを使ってfetch(fetchRequest)をします。成功すると、 NSManagedObjectの配列が帰ってくるので、用意しておいたfetchedArrayの中に入れます。
Screen Shot 2017-07-27 at 11.31.26 AM.png

ちなみにですが、Manual/None設定して、物理的なモデルクラスファイルを生成してたら、fetch()メソッドも勝手に作られて、それを使えばいいので便利!

3. データの書き込み

データの保存も取得と同じで、Contextを通して行います。
Core DataではContextは一時的に保存するためのスクラッチパッドみたいに使われています。
NSMangedObjectContextに書き込んだあと, context.save()をしないと、DataStorageに送られません。

ViewController.swift
//同じviewController内に書く。

var people: [NSManagedObject] = []

func save(name: String) {
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
      return
    }

    let managedContext = appDelegate.persistentContainer.viewContext

    let entity = NSEntityDescription.entity(forEntityName: "Person",
                                            in: managedContext)!

    let person = NSManagedObject(entity: entity,
                                 insertInto: managedContext)

    person.setValue(name, forKeyPath: "name")

    do {
      try managedContext.save()
      people.append(person)
    } catch let error as NSError {
      print("Could not save. \(error), \(error.userInfo)")
    }
}

Screen Shot 2017-07-27 at 12.08.05 PM.png

Contextを使う一番のメリットは、いちいちデータベースからfetch()しなくてもContextにあれば、そこから直接データを取得できることです。

4. データを消去しよう

基本的には、保存と同じで、消去した後にcontext.save()するだけです。

var index:Int = 1 
let person = people[index]  //ローカルに保存したNSManagedObjectの配列のindexをまず指定

context.delete(person)
people.remove(at: index)
(UIApplication.shared.delegate as! AppDelegate).saveContext()

5. アプリが閉じるタイミングでデータを保存しよう

先ほど、NSManagedObjectContext.save()しないとデータベースに情報が送られないので、アプリが閉じる時点でsave()しておくとよい!(クラッシュしてもでもデータを残すため)。AppDelegate.swiftのapplicationWillTerminate()の中に書きましょう。

AppDelegate.swift
  func applicationWillTerminate(_ application: UIApplication) {
    self.saveContext()
  }

  func saveContext () {

    let context = persistentContainer.viewContext
    if context.hasChanges {
      do {
        try context.save()
      } catch {
        let nserror = error as NSError
        fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
      }
    }
  }  

サンプルコードこちら!: Core Data でTodoList

参考文献
Core Data Overview
Getting Started with Core Data Tutorial
Core DataでTodoList