こんにちは。スマートフォンエンジニアのまさです。
最近サーバとやりとりをするアプリを作成していますが、APIをなんども叩くアプリでは、
どうしてもローディングが多くなりがちで、ユーザーにストレスを与えてしまいます。
何度も叩く必要のないAPIはレスポンスをRealmに保存しておいて、より快適なアプリを実現したいですね。
今回は、Realmで多対多のモデルを作る方法をご紹介します。
Realmのインストール
まずは、podでrealmを入れます。
$ pod init $ vi Podfile
pod 'RealmSwift' # 追加
$ pod install
Realmのモデルを作成する
今回は複数の子供が複数のグループに所属しているモデルを作ってみようと思います。 基本的には、多対多は、中間テーブルを用意し、それぞれのテーブルに一対多にすることで実現します。
import RealmSwift class Kid: Object { dynamic var id = 0 dynamic var name = "" var kidGroups = List<KidGroup>() override static func primaryKey() -> String? { return "id" } } class KidGroup: Object { dynamic var group: Group? dynamic var kid: Kid? let ownerKid = LinkingObjects(fromType: Kid.self, property: "kidGroups") let ownerGroup = LinkingObjects(fromType: Group.self, property: "kidGroups") } class Group: Object { dynamic var id = 0 dynamic var name = "" var kidGroups = List<KidGroup>() override static func primaryKey() -> String? { return "id" } }
Realmのモデルに実際にデータを入れてみる
モデルクラスを用意したら、データを入れてみます。 テストなので、viewDidLoadに入れてログを出して確認しています。
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // テストのため、毎回realmのDBを消して再度新規生成されるようにしています let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) let path = paths[0] + "/test.realm" let url = URL(fileURLWithPath: path) let fileManager = FileManager.default if fileManager.fileExists(atPath: path) { try! FileManager.default.removeItem(atPath: path) try! FileManager.default.removeItem(atPath: path + ".lock") try! FileManager.default.removeItem(atPath: path + ".management") } let realm = try! Realm(fileURL: url) // たろう君は、たぬき組に、所属、 // じろう君は、たぬき組、きつね組どちらにも所属させてみる let kid = Kid() kid.id = 1 kid.name = "たろう" let kid2 = Kid() kid2.id = 2 kid2.name = "じろう" let group = Group() group.id = 101 group.name = "たぬき組" let group2 = Group() group2.id = 102 group2.name = "きつね組" let kidGroup = KidGroup() kidGroup.group = group kidGroup.kid = kid kid.kidGroups.append(kidGroup) group.kidGroups.append(kidGroup) let kidGroup2 = KidGroup() kidGroup2.group = group2 kidGroup2.kid = kid2 kid2.kidGroups.append(kidGroup2) group2.kidGroups.append(kidGroup2) let kidGroup3 = KidGroup() kidGroup3.group = group kidGroup3.kid = kid2 kid2.kidGroups.append(kidGroup3) group.kidGroups.append(kidGroup3) try! realm.write() { realm.add(kid) realm.add(kid2) } }
正しく保存されているか確認してみる
それでは、正しく参照できるのか、また中間テーブルが削除されれば きちんと関連がきれるのか、テストしてみようと思います。下記のコードをviewDidLoadの下側に追記してみます。
// クエリ print("Kidのオブジェクトをすべて取得する") let kids = realm.objects(Kid.self) print(kids.count) // => 2 print(kids[0].kidGroups.count) // => 1 print(kids[1].kidGroups.count) // => 2 print("Kidのidが2のオブジェクトを取得する") let kidsId_2 = realm.objects(Kid.self).filter("id = 2") print(kidsId_2[0].kidGroups.count) // => 2 print(kidsId_2[0].kidGroups[0].group?.name ?? "") // => たぬき組 print(kidsId_2[0].kidGroups[1].group?.name ?? "") // => きつね組 print("Groupがきつね組に所属しているkidを取得する") let groupsId_102 = realm.objects(Group.self).filter("id = 102") print(groupsId_102[0].kidGroups.count) // => 1 print(groupsId_102[0].kidGroups[0].kid?.name ?? "") // => じろう print("Kidじろうのgroupきつね組との関連を切ってみる") try! realm.write { realm.delete(groupsId_102[0].kidGroups[0]) } print("きちんと関連が切れているか確認する groupから参照した場合") let updateGroupsId_102 = realm.objects(Group.self).filter("id = 102") print(updateGroupsId_102[0].kidGroups.count) // =>0 print("きちんと関連が切れているか確認する kidから参照した場合") let updateKidsId_2 = realm.objects(Kid.self).filter("id = 2") print(updateKidsId_2[0].kidGroups.count) // => 1 print(updateKidsId_2[0].kidGroups[0].group?.name ?? "") // => たぬき組
正しく参照できているようです。中間テーブルを削除して関連を切れば、kidから参照しても、groupから参照しても正しい結果と
なっているのがわかります。多対多を実現させることができました。
まだまだRealm初心者なので、今後もいろいろと試してみようと思います。
もしここがおかしいとか、こういう書き方もあるよなどありましたら、ご指摘頂けると幸いです。