iOSにユーザーデータを保存する方法と、そのためのコードの書き方: UserDefaults、Core Data、Key Chain、CloudKit

データの保存はiOSアプリの持つ主要な機能です。たとえば、ユーザーが指定した色などの環境設定を保存したり、ウェブサイトのトークンをアプリに保存したり、ToDoリストのアプリを作ってタスクを保存したりすることができます。データをシステムに保存する方法はいくつもあります。

## 目次

[User Defaults](https://qiita.com/MaShunzhe/items/e7263507e07919a6adab#user-defaults)
[Core Data](https://qiita.com/MaShunzhe/items/e7263507e07919a6adab#core-data)
[Keychain](https://qiita.com/MaShunzhe/items/e7263507e07919a6adab#keychain-キーホルダー)
[iCloud CloudKit](https://qiita.com/MaShunzhe/items/e7263507e07919a6adab#cloudkit)

この記事では、上記の方法それぞれについて、サンプルコードを使って解説します。

## User Defaults

### 主な用途

わずか2〜3行のコードで、`String`, `Int`, `Bool`, やどんな `Data` オブジェクトも保存・取り出しができます。

通常は、ユーザーがオンボーディングを終えたかどうかや、天気アプリでのユーザーのデフォルト都市等、ユーザーのプリファレンスを保存するのに使用されます。

### 長所

- わずか数行のコードで簡単に実装
- App Extensions 間でも簡単に共有
- クラウドにデータを保存でき、ユーザーの所有デバイス間で同期可能（ 同じApple ID でサインイン）

### 短所

シンプルなデータの保存のみに使います。データベーススキームはありません。また Apple によると、`UserDefaults` ストレージが512 KBに達すると警告が発せられます。そして Apple のドキュメンテーションには、ストレージが1MBに達するとアプリが終了するとあります。

>Currently, there is only a size limit for data stored to local user defaults on tvOS, which posts a warning notification when user defaults storage reaches 512kB in size, and terminates apps when user defaults storage reaches 1MB in size. 
> https://developer.apple.com/documentation/foundation/userdefaults/1617187-sizelimitexceedednotification

しかし、Stack Overflow のユーザーの一人は、1MB以上のデータを保存してもクラッシュを起こしていないと報告しています。

> https://stackoverflow.com/a/41615483

ですから、ユーザーのプリファレンスや文字列・Int値をいくらか保存したい程度であれば心配することはありません。

### 使い方

```swift
//To store a value
UserDefaults.standard.set("Tokyo", forKey: "currentCity")
//To read a value
let currentCity = UserDefaults.standard.string(forKey: "currentCity")
```

### （クラウド同期バージョン）の使い方

#### iCloudの機能を有効にする

プロジェクト設定の `Signing & Capabilities` タブで、プラスボタンをクリックします。

<img width="1426" alt="Screen Shot 2020-05-27 at 2.23.12 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/9ebe7b83-0c2d-df84-93df-c413306c9973.png">

そして、`iCloud` 機能を追加します

<img width="692" alt="Screen Shot 2020-05-27 at 2.26.28 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/37d2d411-28a8-ffc5-8372-1fe95a893dbf.png">

`Key-value storage` をオンにします

<img width="611" alt="Screen Shot 2020-05-27 at 2.26.54 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/4fb85b17-c7ca-2d87-ef63-8ce3ff82dc4e.png">

そして、すべての`UserDefaults.standard`を`NSUbiquitousKeyValueStore()`に置き換えるだけです：

```swift
let store = NSUbiquitousKeyValueStore()
store.set("Tokyo", forKey: "currentCity")
let currentCity = store.string(forKey: "currentCity")
```

### 資源

[Appleの公式文書](https://developer.apple.com/documentation/foundation/userdefaults)

## Core Data

### 主な用途

`Core Data` には幅広い用途があります。私の個人的な開発では、`Core Data` を使ってユーザーの日記、ToDoアイテム、ブックマークを保存しています。

`Core Data` はユーザーの情報を既存の多くのアプリに保存するために広く使われています。

### 長所

- 構造化データの保存（データベーススキームを設計する必要があります）
- 数分以内にクラウド同期サポートを有効にできる

### 短所

- データベーススキームを変更するたびに、新しいデータベースバージョンを作成する必要があります。そうしないとアプリがクラッシュしてしまいます。

### 使い方

#### 新しいアプリ

新しいアプリを作成する場合は、プロジェクトの作成時に `Use Core Data` を選択できます。

<img width="766" alt="Screen Shot 2020-05-27 at 2.35.06 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/8c5eec98-42e2-ed8d-6567-aed7575e7bf2.png">

#### 既存のアプリ

また、既存のアプリに `Core Data` を簡単に追加できます。

最初に、データモデルファイルを作成します。キーボードのcommand-Nキーを押して、`Data Model` を検索します。

<img width="753" alt="Screen Shot 2020-05-27 at 2.37.09 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/9ee239d9-82a6-fd94-c453-5cee988d35b7.png">

新しい `Data Model` にあなたのアプリの名前をつけてください。例えば、ここでは私のアプリの名前は `CoreDataDemo` なので、私は `CoreDataDemo.xcdatamodeld` を作りました。

そして `AppDelegate.swift` ファイルに次の行を追加します。

```swift
// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "CoreDataDemo")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            print(error.userInfo)
        }
    })
    return container
}()
```

この行で、 `CoreDataDemo` をあなたのアプリの名前に置き換えてください。

```swift
let container = NSPersistentContainer(name: "CoreDataDemo")
```

#### データベース構造の作成

To-Doアイテムごとに次のプロパティを格納するTo-Doアプリを作成するとします: 

- To-Doタスク名
- タスクの担当者
- タスクの期限

##### 1. `.xcdatamodeld` ファイルを開き、`Add Entity` をクリックします。

<img width="1512" alt="Screen Shot 2020-05-27 at 2.42.46 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/b6d8e322-df60-d8cf-acfc-3fea63c77c43.png">

##### 作成した新規エンティティ・アイテムに `TodoItem` という名前を付けます。

作成した新しいアイテムをダブルクリックし、名前を付けるだけです。この例では、 `TodoItem` という名前を付けます。

##### 新しいプロパティを追加する

`Properties` セクションのプラスアイコンをクリックして、新しいプロパティを追加します。

<img width="1512" alt="Screen Shot 2020-05-27 at 2.44.30 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/61024a4d-a218-783a-3bd6-34f011f45aa7.png">

ここでは、To-Doアプリに次のプロパティが必要です。

| プロパティ名  | タイプ   |
|---|---|
| todoTaskName   | String  |
| personName  | String  |
| taskDeadline   | Date  |

タイプを設定するには、プロパティ名の横にあるドロップダウンメニューをクリックします:

<img width="672" alt="Screen Shot 2020-05-27 at 2.47.09 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/3687488d-867a-bd23-3601-f78bf4daa965.png">

このような感じになります。

<img width="670" alt="Screen Shot 2020-05-27 at 2.48.28 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/f23e43f1-e648-c37a-8142-009fac842439.png">

##### Entity クラスのコードファイルを生成

1. 作成した新しいエンティティ項目を選択します。右側のパネルで最後のセクションのアイコンを選択します :

<img width="1512" alt="Screen Shot 2020-05-27 at 3.05.23 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/8c6d3648-da3c-1a81-36a4-c6b4d34ba75b.png">

2. `Codegen` 設定を `Manual/ None` に変更

以前の設定:

<img width="254" alt="Screen Shot 2020-05-27 at 3.08.57 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/f1087650-bced-51d1-32e5-bdad66dc0d26.png">

新しい設定:

<img width="258" alt="Screen Shot 2020-05-27 at 3.10.38 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/1dcc598b-8828-f596-fa51-e0926eeca84b.png">

3. クラス定義ファイルを生成

最上部のメニューで `Editor` をクリックし、`Create NSManagedObject subclass...` をクリックします

こうしたサブクラスを生成することで、`TodoItem` を新規オブジェクトの作成に直接使用できるようになります。

<img width="961" alt="Screen Shot 2020-05-27 at 3.11.37 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/4346c51e-142e-6ee2-7747-df2ed8da5199.png">

ファイルナビゲーターに以下のファイルが表示されます :

<img width="272" alt="Screen Shot 2020-05-27 at 3.13.31 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/07d67d8d-03b1-7f59-de98-ea9e85fefd83.png">

##### 新規レコードを作成

```swift
func addNewToDoItem(todoTaskName: String, personName: String, taskDeadline: Date) {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let newItem = TodoItem(context: context)
    newItem.todoTaskName = todoTaskName
    newItem.personName = personName
    newItem.taskDeadline = taskDeadline
    do {
        try context.save()
    } catch {
        print(error.localizedDescription)
    }
}
```

##### 既存のレコードを取得

```swift
func getAllPlannerDays() -> [TodoItem] {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let savedPlaceFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "TodoItem")
    do {
        let result = try context.fetch(savedPlaceFetch)
        if let convertedResult = result as? [TodoItem] {
            return convertedResult
        }
    } catch {
        return []
    }
    return []
}
```

##### 変更

```swift
func modifyObject(_ object: TodoItem, newDeadline: Date) {
    object.taskDeadline = newDeadline
    //保存する
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    do {
        try context.save()
    } catch {
        print(error.localizedDescription)
    }
}
```

##### 削除

```swift
func deleteObject(_ object: NSManagedObject) {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    context.delete(object)
    //保存する
    do {
        try context.save()
    } catch {
        print(error.localizedDescription)
    }
}
```

プログラムから `TodoItem` をご自身のエンティティ名に置き換えてください。

##### 特定のレコードをクエリ中

すべての `TodoItem` に対し `personName` を `ネコノヒー` としてクエリしましょう

```swift
func getToDoItem(withPerson: String) -> [TodoItem] {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let savedPlaceFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "TodoItem")
    savedPlaceFetch.predicate = NSPredicate(format: "personName = %@", withPerson)
    do {
        let result = try context.fetch(savedPlaceFetch)
        return result as? [TodoItem]
    } catch {
        return []
    }
    return []
}

let allTodo = getToDoItem(withPerson: "ネコノヒー")
```

#### ユーザーのコアデータをクラウドに同期する

すでにここにガイドを書いています:

https://qiita.com/MaShunzhe/items/2018455cab0803d78442

### 新しいデータベースバージョンの作成

データベースの新しいバージョン（プロパティの追加、変更、削除、または新しいエンティティの作成）を作成するたびに、データベースの新しいバージョンを作成する必要があります。

データベースモデルファイルを選択した状態で、「Editor エディター」をクリックし、「Add Model Version モデルバージョンの追加」をクリックします。

次に、ファイルナビゲーターで、データベースモデルファイルの左側にある三角形のアイコンをクリックします。

作成した新しいデータモデルバージョンをクリックします。

右側のパネルでファイルアイコンをクリックし、「Model Version モデルバージョン」を見つけて、作成した新しいモデルバージョンに切り替えます。

左側のパネルで、作成したバージョンに緑のチェックマークが付いていることを確認してください。


### 資源

[Appleの公式文書](https://developer.apple.com/documentation/coredata)

[クラウドサポート付きCore Data](https://developer.apple.com/videos/play/wwdc2019/202/)

[Core Dataでアプリ制作](https://developer.apple.com/videos/play/wwdc2019/230/)

## KeyChain (キーホルダー)

### 主な用途

ユーザーの機密情報を保存する場合は、Key Chainに保存することをお勧めします。Keychainは、iOSがユーザーのウェブサイトのユーザー名とパスワードを保存する場所です。暗号化されており、ユーザーのパスコードまたはFace ID （Touch ID）でのみアクセスできます。

### 長所

- セキュリティー
- 保存された認証情報は、ユーザーのデバイス間で同期できます。

### 短所:

- ユーザーはMac OS KeychainアプリでKeychainに保存したデータを見ることができます
- 通常、開発者はkeychainに文字列を保存します。データを保存することはお勧めしません。

### 使い方

keychainにアクセスするのに必要なコードはいくつかありますが、私はkeychainに簡単にアクセスできるオープンソースのGithubリポジトリを見つけました。

https://github.com/evgenyneu/keychain-swift

### 資源

[Appleの公式文書](https://developer.apple.com/documentation/securityinterface/sfkeychainsavepanel/1580412-keychain?language=occ)

## CloudKit

### 長所

- - データをプライベート・データベース、共有データベース、またはパブリック・データベース（すべてのユーザーが他のユーザーのレコードをフェッチできる）に保存できます。
-  CloudKitはAppleの無料クラウドサービスです。
- CloudKitプライベート・データベースへの保存は、ユーザー自身のiCloudストレージスペースに対してカウントされます。
- CloudKitパブリック・データベースへの保存は、割り当て制限に対してカウントされます。
- ユーザーがアプリを削除してから再度インストールすると、CloudKitに保存されているデータをフェッチできます。

### 短所

- ユーザーはオンラインである必要があります。また、CloudKitデータベースの使用にはサイズ制限があります。

https://developer.apple.com/icloud/cloudkit/

### 実装

私は以前、「CloudKit」を使用してユーザーの情報を保存および取得する方法についてのガイドを紹介しました：

https://qiita.com/MaShunzhe/items/bc21527259c5da7f66ed

https://qiita.com/MaShunzhe/items/9291c86a3d0442ddf759

### 資源

[Appleの公式文書](https://developer.apple.com/documentation/cloudkit)

[CloudKitダッシュボードの使用](https://developer.apple.com/videos/play/wwdc2017/226/)

---

:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:sunny: [私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。](http://mszpro.com/ioskiji/)
<img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">