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

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

## Core Dataとは？

Core Dataとはモデルオブジェクトを扱うためのフレームワークです！Core Data発表されてからモデルレイヤーを作るのが楽になった（はず）。Core Dataは[パーシスタンス](http://wa3.i-3-i.info/word13634.html)を使っていて、これによりモデルオブジェクトの状態をディスクに維持している。

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

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](https://qiita-image-store.s3.amazonaws.com/0/158705/c8ce10d5-2c0d-69b0-80d6-39a2df46e326.png)





## こんなことを説明します

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

## 図でわかるCore Dataクラス

![Screen Shot 2017-07-26 at 12.40.46 PM.png](https://qiita-image-store.s3.amazonaws.com/0/158705/8df109ae-734e-c46f-3a19-667ff8911e1b.png)

全てのコンポーネントがつながっているこの状態を**Core Data Stack**と呼ばれています。
このスタックは、**Object Graph Management**(左)と**Persistance**(パーシスタンス)（右）の２パートに分かれています。その二つを繋いでいるのが**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](https://qiita-image-store.s3.amazonaws.com/0/158705/549f5921-b971-a596-4357-fb72053fcec0.png)

図でも説明しましたが、データタイプがないときは、"transformable"で設定しておいて、取得時に本来のデータタイプに置き換えます。本来のデータタイプの右のエディターで設定しておきましょう。
![Screen Shot 2017-07-26 at 2.35.40 PM.png](https://qiita-image-store.s3.amazonaws.com/0/158705/21565118-27bc-6e12-49c9-20f509e2bc2a.png)

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

Entityを選ぶと右側に**Manual/None**, **Class Definition** **Category/Extention**があります。どれかを選ぶことによって、ファイルの数とデータの書き方がちょっと変わるのでこの２パターンを比較しましょう。
![Screen Shot 2017-07-26 at 2.58.57 PM.png](https://qiita-image-store.s3.amazonaws.com/0/158705/0705f207-179b-8ccb-6559-bfde23e6bc65.png)

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

このプロジェクト分かりやすかったです↓
[モデルクラスファイルなしのプロジェクト例](https://www.raywenderlich.com/145809/getting-started-core-data-tutorial)

### Manual/None
これを選んで、Modelクラスファイルを生成すると、自分で作ったEntitiesとModelと連動させることができます。
生成ってなんやねん？
Editor > Create NSManaged Object Subclass...を選んで進んでいくと勝手に自分で作ったモデルファイルを元にモデルクラスを生成してくれます。
![Screen Shot 2017-07-26 at 4.19.43 PM.png](https://qiita-image-store.s3.amazonaws.com/0/158705/80305e40-7e28-6280-bad1-b48bed12b85d.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を使っているプロジェクト例](https://github.com/cicccDerrick/iOS/tree/master/PhotoLibrary)


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


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


## 使い方

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

![Screen Shot 2017-07-28 at 11.15.49 AM.png](https://qiita-image-store.s3.amazonaws.com/0/158705/46d1f2f6-1432-41f8-b716-c6ea000fafee.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**メソッドで最初にデータベースにストアされいるファイルを読み込んでます。
[最初の図を見ると](http://qiita.com/Saayaman/items/ea437032afaeddd0cf50#%E5%9B%B3%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8Bcore-data%E3%82%AF%E3%83%A9%E3%82%B9)、**NSPersistantCoordinator**を使うんじゃないの？って思うかもですが、**Container**を使って、Coordinator, ManagedObject, PersistantStoresの取得ができるっぽいです。↓
![Screen Shot 2017-07-26 at 1.42.31 PM.png](https://qiita-image-store.s3.amazonaws.com/0/158705/4f497d9b-7978-f1a3-1fd7-6b20d8cf6bb5.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](https://qiita-image-store.s3.amazonaws.com/0/158705/90093601-5cc1-1a68-1056-e7a7727b54af.png)

ちなみにですが、[Manual/None設定して](http://qiita.com/Saayaman/items/ea437032afaeddd0cf50#manualnone)、物理的なモデルクラスファイルを生成してたら、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](https://qiita-image-store.s3.amazonaws.com/0/158705/05bc2af4-7562-1ed4-f8f9-398f6b8a1785.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](https://github.com/Saayaman/TodoApp/blob/master/CoreDataToDoApp/CoreDataToDoApp/ViewController.swift)


参考文献
[Core Data Overview](https://www.objc.io/issues/4-core-data/core-data-overview/)
[Getting Started with Core Data Tutorial](https://www.raywenderlich.com/145809/getting-started-core-data-tutorial)
[Core DataでTodoList](https://blog.bobthedeveloper.io/beginners-guide-to-core-data-in-swift-3-85292ef4edd)