Edited at

Realmで配列を扱う!?

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

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


まず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的にわかりやすく説明すると

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

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

言葉で説明すると

「'夏休みの宿題' というタスクが '英語','算数','社会' のチケットを持っている」

という感じでしょうか?

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


モデルの定義

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

インストールなどについてはわかりやすい記事がたくさんあるのでご自身で調べてみてください

https://qiita.com/leegun/items/70414223b2339b6052ee

まずはクラスを作っていきます

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

import RealmSiwf

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

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

Realmでモデルを定義する際には

@objc dynamic

と記載します

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

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

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

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のインスタンスを取得し、そこにデータを挿入していきます

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(_:)を使用します。

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"で追加します。

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)」を使用して特定の値を削除してみたいと思います

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 = 社会;
}
);
}
)

ちょっとネストがきつくなってしまいましたがご愛嬌ということで


おまけ!

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

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」を使用してあげるとより簡潔に取り出せます!

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)
}