Realmで配列を扱う！？

はい！タイトル詐欺です！Realmで配列を扱うことはできません！

じゃあどうするのかというと、「List」という型を使ってデータに紐付けをしていくというのが今回の内容です。

（20/05/08 14:03追記）

データの更新処理についての記述がなかったので追記させていただきました。
@masa_unofficial さんコメントいただきありがとうございます！

##まずRealmとは
簡単にいうとデータベースです

Realm公式:https://realm.io/jp/
RealmSwiftリファレンス:https://realm.io/docs/swift/latest/api/index.html

SwiftでRealmを扱うには「RealmSwift」というライブラリを使用します。

##RealmSwiftで扱える「型」
詳しくはリファレンスを見ていただきたいんですが、主に扱える型としては
・**String**
・**Int**
・**Bool**
・**Date**
・**List**
などになります

今回はこの「**List**」に焦点を当てていきます

##Listについて
公式リファレンスの説明をgoogleさんに翻訳してもらいました

**「List 1対多の関係を定義するために使用されるRealmのコンテナタイプです。」**

よくわかんないですね

Swift的にわかりやすく説明すると


```swift
["task": "夏休みの宿題", "ticket": ["英語", "算数", "社会"]]
```

ちょっと違うかもしれないですけどこんな感じですかね？

言葉で説明すると
*「'夏休みの宿題' というタスクが '英語','算数','社会' のチケットを持っている」*
という感じでしょうか？

余計わかりにくなった？とりあえずこれをRealmで書いてみます

##モデルの定義

それでは早速Realmの定義を書いていきましょう！

インストールなどについてはわかりやすい記事がたくさんあるのでご自身で調べてみてください
例https://qiita.com/leegun/items/70414223b2339b6052ee

まずはクラスを作っていきます
今回は先ほど例に出したタスクとチケットのモデルを定義していきます

```swift
import RealmSiwf

class Task: Object {  
    @objc dynamic var taskTitle: String = ""
}

class Ticket: Object {
    @objc dynamic var ticketTitle: String = ""
}
```

Realmでモデルを定義する際には
`@objc dynamic`
と記載します

タスクとチケットのモデルが定義できました！

ただこれだとタスクとチケットが紐付いていません・・・

Listを使って１対多の関係を提示してあげましょう

```swift
import RealmSiwf

class Task: Object {  
    @objc dynamic var taskTitle: String = ""
    //Listの定義
    let tickets = List<Ticket>()
}

class Ticket: Object {
    @objc dynamic var ticketTitle: String = ""
}
```

Listを定義する際は
`List<Element>`
としてあげる必要があります

これで定義は完了しました！

では実際にデータを追加したり参照したりしてみましょう

##データモデルの追加

Realmのインスタンスを取得し、そこにデータを挿入していきます

```swift
do {
    //インスタンスの取得
    let realm = try Realm()
    let dictionary: [String: Any] =
        ["taskTitle": "夏休みの宿題",
         "tickets": [["ticketTitle": "算数"],
                     ["ticketTitle": "英語"],
                     ["ticketTitle": "社会"]]
        ]

    let task = Task(value: dictionary) //Taskモデルのインスタンスの作成

    //書き込み処理
    try! realm.write { 
        realm.add(task)
        print(task)
    }
}
catch {
    print(error)
}
```
*プリント結果*

```

Task {
	taskTitle = 夏休みの宿題;
	tickets = RLMArray&lt;Cat&gt; &lt;0x1c0306150&gt; (
		[0] Ticket {
			ticketTitle = 算数;
		},
		[1] Ticket {
			ticketTitle = 英語;
		},
		[2] Ticket {
			ticketTitle = 社会;
		}
	);
}
```

Taskのインスタンスにタスク名とチケットを挿入して、実際のデータに書き込んでいます。
データに書き込んだり、またそのデータを削除したりなどの処理は
`realm.write{}`
の中にコーディングする必要があるので気をつけてください！

##データモデルの取得(Results)
追加したデータモデルを取得してみましょう

Realmデータベースに保存されている特定のモデルクラスのオブジェクトを全て取得するにはRealmクラスのobjects(_:)を使用します。


```swift
do {
    //インスタンスの取得
    let realm = try Realm()

    //オブジェクトの取得
    let results = realm.objects(Task.self)
    print(results)
}
catch {
    print(error)
}
```

*プリント結果*

```
Results<Task> <0x104423670> (
	[0] Task {
		taskTitle = 夏休みの宿題;
		　　tickets = RLMArray<Ticket> <0x1c41193e0> (
		　　[0] Ticket {
		　　　　ticketTitle = 算数;
		　　},
		　　[1] Ticket {
		　　　　ticketTitle = 英語;
		　　},
		　　[2] Ticket {
		　　　　ticketTitle = 社会;
		　　}
		);
	}
```

##List側の削除と追加
まずはチケットを追加する処理を書いてみましょう

通常Realmでデータを追加するときは"add"を使用するのですが、List型の場合"**append**"で追加します。

```swift
do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)
    
    let ticket = Ticket(value: ["ticketTitle": "国語"]) //Ticketモデルのインスタンスの作成
    
    print("追加前",results)
    // 追加処理
    try! realm.write {
        for task in results {
            task.tickets.append(ticket)
        }
    }
    print("追加後",results)
}
catch {
    print(error)
}
```

*プリント結果*

```
追加前 Results<Task> <0x7ff8c650f470> (
	[0] Task {
		taskTitle = 夏休みの宿題;
		tickets = List<Ticket> <0x600003d84360> (
			[0] Ticket {
				ticketTitle = 算数;
			},
			[1] Ticket {
				ticketTitle = 英語;
			},
			[2] Ticket {
				ticketTitle = 社会;
			}
		);
	}
)
追加後 Results<Task> <0x7ff8c650f470> (
	[0] Task {
		taskTitle = 夏休みの宿題;
		tickets = List<Ticket> <0x600003d84a20> (
			[0] Ticket {
				ticketTitle = 算数;
			},
			[1] Ticket {
				ticketTitle = 英語;
			},
			[2] Ticket {
				ticketTitle = 社会;
			},
			[3] Ticket {
				ticketTitle = 国語;
			}
		);
	}
)
```

では次に削除してみましょう！
Listには「removeLast()」や「removeFirst()」などのメソッドがあり最初のデータや最後のデータなどを指定して削除できます
今回は「remove(at:Int)」を使用して特定の値を削除してみたいと思います

```swift
do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)
    
    print("削除前",results)
    // 削除処理
    try! realm.write {
        for task in results {
            for (index, ticket) in task.tickets.enumerated() {
                if ticket.ticketTitle == "国語" {
                    task.tickets.remove(at: index)
                }
            }
        }
    }
    print("削除後",results)
}
catch {
    print(error)
}
```
*プリント結果*

```
削除前 Results<Task> <0x7fc26af135e0> (
	[0] Task {
		taskTitle = 夏休みの宿題;
		tickets = List<Ticket> <0x600003d5b9f0> (
			[0] Ticket {
				ticketTitle = 算数;
			},
			[1] Ticket {
				ticketTitle = 英語;
			},
			[2] Ticket {
				ticketTitle = 社会;
			},
			[3] Ticket {
				ticketTitle = 国語;
			}
		);
	}
)
削除後 Results<Task> <0x7fc26af135e0> (
	[0] Task {
		taskTitle = 夏休みの宿題;
		tickets = List<Ticket> <0x600003d51950> (
			[0] Ticket {
				ticketTitle = 算数;
			},
			[1] Ticket {
				ticketTitle = 英語;
			},
			[2] Ticket {
				ticketTitle = 社会;
			}
		);
	}
)
```
ちょっとネストがきつくなってしまいましたがご愛嬌ということで

##データの更新
保存したデータのプロパティを更新することができます。

まずはタスクのタイトルを変えてみます。

```swift
do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)
    
    print("更新前",results)
    // 削除処理
    try! realm.write {
        result.first?.taskTitle = "晩ご飯の食材"  //プロパティの更新
    }
    print("更新後",results)
}
catch {
    print(error)
}
```

*プリント結果*

```
更新前 Results<Task> <0x7fc26af135e0> (
	[0] Task {
		taskTitle = 夏休みの宿題;
		tickets = List<Ticket> <0x600003d5b9f0> (
			[0] Ticket {
				ticketTitle = 算数;
			},
			[1] Ticket {
				ticketTitle = 英語;
			},
			[2] Ticket {
				ticketTitle = 社会;
			}
		);
	}
)
更新後 Results<Task> <0x7fc26af135e0> (
	[0] Task {
		taskTitle = 晩ご飯の食材;
		tickets = List<Ticket> <0x600003d51950> (
			[0] Ticket {
				ticketTitle = 算数;
			},
			[1] Ticket {
				ticketTitle = 英語;
			},
			[2] Ticket {
				ticketTitle = 社会;
			}
		);
	}
)
```

次にListであるチケットを更新してみましょう。
`setValue(_ value: Any?, forKey key: String)`
このメソッドを使うことで一括で値を更新することができます。

```swift
do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)
    
    let ticketArray: [Dictionary<String, String>] = 
     [["ticketTitle": "魚"],
      ["ticketTitle": "醤油"],
      ["ticketTitle": "味噌"]]

    print("更新前",results)
    // 更新処理
    try! realm.write {
        results.first?.setValue(ticketArray, forKey: "tickets")
    }
    print("更新後",results)
}
catch {
    print(error)
}
```

*プリント結果*

```
更新前 Results<Task> <0x7fc26af135e0> (
	[0] Task {
		taskTitle = 晩ご飯の食材;
		tickets = List<Ticket> <0x600003d5b9f0> (
			[0] Ticket {
				ticketTitle = 算数;
			},
			[1] Ticket {
				ticketTitle = 英語;
			},
			[2] Ticket {
				ticketTitle = 社会;
			}
		);
	}
)
更新後 Results<Task> <0x7fc26af135e0> (
	[0] Task {
		taskTitle = 晩ご飯の食材;
		tickets = List<Ticket> <0x600003d51950> (
			[0] Ticket {
				ticketTitle =　魚;
			},
			[1] Ticket {
				ticketTitle = 醤油;
			},
			[2] Ticket {
				ticketTitle = 味噌;
			}
		);
	}
)
```

##おまけ！
タイトルで配列云々と言っていので、上記で作成したデータを配列に戻してみます

```swift
do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)
    
    var taskArray = ["taskTitle":"", "tickets":[]] as [String : Any]
    var ticketArray: Array<String> = []
    
    for task in results {
        taskArray["taskTitle"] = task["taskTitle"]
        for ticket in task["tickets"] as! List<Ticket> {
            ticketArray.append(ticket.ticketTitle)
        }
    }
    taskArray["tickets"] = ticketArray
    print(taskArray)
}
catch {
    print(error)
}
```
*プリント結果*

```
["taskTitle": 夏休みの宿題],"tickets": ["算数", "英語", "社会"]]
```
こんな感じでしょうか？

##最後に

Realmは直感的にデータをいじれるので使っていて楽しいです
自分もまだまだ理解できていないことがたくさんあるので習得に励んでいこうと思います！

皆さんも良いRealmライフを！！

##追記
@t_naganoさんからのご指摘！
Realmからデータを取り出すとき、「map」を使用してあげるとより簡潔に取り出せます！

```swift
do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)
    
    var taskArray = ["taskTitle":"", "tickets":[]] as [String : Any]
    var ticketArray = [String]()
    
    for task in results {
        taskArray["taskTitle"] = task["taskTitle"]
        // for ticket in task["tickets"] as! List<Ticket> {
        //     ticketArray.append(ticket.ticketTitle)
        // }
        ticketArray = task.tickets.map { $0.ticketTitle } // mapを使って取り出す
    }
    taskArray["tickets"] = ticketArray
    print(taskArray)
}
catch {
    print(error)
}

```