RealmSwift 入門の巻 〜イラストでわかりやすく〜
Realmを使ってた際のメモ書を、イラスト付き で載っけておきます。
コピペで使えます。
大体は Realm公式に書いてあることだけど。
RealmSwiftとは
Podsライブラリの一つ。
既存の保存方法に UserDefaults がありますが、なぜこちらではなく、 RealmSwiftを使う必要性があるのか。
UserDefaults は、Int
や String
などの値をそのまま保存するのみです。
独自のクラス形式を保存することはできません。
RealmSwift は、独自のクラス形式の状態で保存できます。
また、オブジェクト同士の関係性(リレーション) も、同時に管理できます。
あと、サービスの Realm と連携できたり…(本記事では紹介しません)
Realmの作成
今回は User
、Product
というクラスを作り、例を説明します。
import RealmSwift
class User: Object {
@objc dynamic var id = ""
@objc dynamic var name = ""
@objc dynamic var biography = ""
@objc dynamic var productsNum = 0
}
class Product: Object {
@objc dynamic var id = ""
@objc dynamic var title = ""
@objc dynamic var itemDescription = ""
@objc dynamic var createdAt = Date()
@objc dynamic var likesNum = 0
@objc dynamic var creator: User? //Realmモデルの中に、別のRealmモデルがある
}
オブジェクトの作成方法は、通常のそれと同じです。
(他の書き方もありますが、それに関しては別の記事を参考してね。)
let user = User()
user.id = "1"
user.name = "むらびと"
user.biography = "伝説の(せん)とうみん(ぞく)"
let product = Product()
product.id = "1"
product.title = "りんご"
product.itemDescription = "ぼくの島の特産物です。みかん求む。"
product.creator = user
Realmに保存
オブジェクトを生成したはいいですが、まだ保存をしていません。
なので、Realmに一度保存してみましょう。
やり方は簡単。さっき作ったオブジェクト を使うだけ。
writeトランザクション中に add
しましょう。
let realm = try! Realm()
// 今回は user を追加してみる。
try! realm.write {
realm.add(user)
}
以上!!
簡単ですね。
これで、user
は マネージドオブジェクトManagedObject
という Realm管理状態 なりました。
Realmから検索・取得
では、早速登録したオブジェクトを取り出してみましょう。
クエリ検索というやつです。
取得時は Results<Element>
になっています。
配列 なので、必要なオブジェクトを取り出しましょう。
クラス毎の全データ検索
let realm = try! Realm()
// 全データ検索
let results = realm.objects(User.self)
print(results)
Results<User> <0x155f08ad0> (
[0] User {
id = 1;
name = むらびと;
biography = 伝説の(せん)とうみん(ぞく);
productsNum = 0;
}
)
さらに条件で検索
let realm = try! Realm()
// 全データ検索
let results = realm.objects(User.self)
// 今回は、idで検索
let id = "1"
let predicate = NSPredicate(format: "id == %@", id)
if let user = results.filter(predicate).first {
print(user)
} else {
print("no data")
}
User {
id = 1;
name = むらびと;
biography = 伝説の(せん)とうみん(ぞく);
productsNum = 0;
}
条件検索も色々指定できます。
それについては、『NSPredicate realm 条件』🔎
Realmに更新
Realmに 保存したオブジェクト、
Realmから 検索・取得したオブジェクト、
これらは、マネージドオブジェクトManagedObject
という Realm管理状態 となっています。
そのため、更新は writeトランザクションの中 で行う必要があります。
print(user.name) // -> むらびと
try! realm.write {
user.name = "たぬ○ち"
}
実際に確認してみると…
let id = "1"
let predicate = NSPredicate(format: "id == %@", id)
let realm = try! Realm()
if let user = realm.objects(User.self).filter(predicate).first {
print(user)
}
User {
id = 1;
name = たぬ○ち;
biography = 伝説の(せん)とうみん(ぞく);
productsNum = 0;
}
はい。変わってますね。
ちなみに、更新処理の書き方にも色々あります。
Realmから削除
削除する(単体)
writeトランザクション中に delete
します。
これで、マネージドオブジェクトManagedObject
状態が解除され、Realmから完全に削除されます。
let realm = try! Realm()
try! realm.write {
realm.delete(user)
}
全て削除する
全部消したい場合は deleteAll
で全て消えます。
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
実際に確認してみると…
let id = "1"
let predicate = NSPredicate(format: "id == %@", id)
let realm = try! Realm()
if let user = realm.objects(User.self).filter(predicate).first {
print(user)
} else {
print("no data")
}
no data
消えています。大丈夫ですね。
ここからが本番
ここまで淡々と説明してきましたが、ある一点について全く説明していません。
それが、オブジェクト間の関係性(リレーション) です。
import RealmSwift
class User: Object {
@objc dynamic var id = ""
@objc dynamic var name = ""
@objc dynamic var biography = ""
@objc dynamic var productsNum = 0
}
class Product: Object {
@objc dynamic var id = ""
@objc dynamic var title = ""
@objc dynamic var itemDescription = ""
@objc dynamic var createdAt = Date()
@objc dynamic var likesNum = 0
@objc dynamic var creator: User? //Realmモデルの中に、別のRealmモデルがある
}
これらに書いてあるとおり、Product
の中には User
が格納できます。
つまり、この2つには 関係性がある ということですね。
格納した状態で、実際にrealmに登録してみると…??
オブジェクト間の関係性(リレーション)
let user = User()
user.id = "1"
user.name = "むらびと"
user.biography = "伝説の(せん)とうみん(ぞく)"
let product = Product()
product.id = "1"
product.title = "りんご"
product.itemDescription = "ぼくの島の特産物です。みかん求む。"
product.creator = user
let realm = try! Realm()
// 今回は product を追加してみる。
try! realm.write {
realm.add(product)// ← product だけでなく、中の user も別に登録される…??
}
Product
の中には、 User
が入っています。
この状態で、Product
だけをRealmに追加すると、一体どうなるのでしょう…??
気になりますね…
気になるでしょう?
おや…???
product
を追加しただけのはずですが、
結果としては、
product
、user
の両方が、Realm に保存されます。
実際に確認してみると…
let id = "1"
let predicate = NSPredicate(format: "id == %@", id)
let realm = try! Realm()
// User が追加されているのか確認します。
if let user = realm.objects(User.self).filter(predicate).first {
print("get user")
print(user)
} else {
print("no user")
}
// Product が追加されているのか確認します。
if let product = realm.objects(Product.self).filter(predicate).first {
print("get product")
print(product)
} else {
print("no product")
}
get user
User {
id = 1;
name = むらびと;
biography = 伝説の(せん)とうみん(ぞく);
productsNum = 0;
}
get product
Product {
id = 1;
title = りんご;
itemDescription = ぼくの島の特産物です。みかん求む。;
createdAt = 2021-03-13 10:25:37 +0000;
likesNum = 0;
creator = User {
id = 1;
name = むらびと;
biography = 伝説の(せん)とうみん(ぞく);
productsNum = 0;
};
}
product
は、user
との関係性を維持したまま、両方とも保存されています。
未登録のRealmオブジェクト(アンマネージドオブジェクト)は、
writeトランザクション中の add
で、中身ごと保存されます。
ただ、ここで一つ問題があります。
id="1"
の user
が既に保存されていた状態で、この操作を行った場合…どうなるでしょう?
答えは、同じidのuser
が、複数存在することになります。
これを防ぐためには…
プライマリキー(主キー)
まあ、簡単に言うと ID を設定するときに、同じIDを持つオブジェクトが、複数存在しないようにする ってこと。
ユーザーIDを作りたいときには必要ですね。
設定の方法は簡単。
Realmモデル内で、primaryKey()
をオーバーライドするだけ。
import RealmSwift
class User: Object {
@objc dynamic var id = ""
@objc dynamic var name = ""
@objc dynamic var emailAddress = ""
@objc dynamic var biography = ""
@objc dynamic var imageUrl = ""
// オーバーライドし、プライマリキーにしたい変数名を返す。
override static func primaryKey() -> String? {
return "id"
}
}
let user = User()
user.id = "1"
user.name = "むらびと"
user.biography = "伝説の(せん)とうみん(ぞく)"
let product = Product()
product.id = "1"
product.title = "りんご"
product.itemDescription = "ぼくの島の特産物です。みかん求む。"
product.creator = user
let realm = try! Realm()
try! realm.write {
realm.add(product) // <- エラーの危険性
}
ただ、注意点が1つ。(だけじゃないけど)
保存されているRealmオブジェクトと、
同一のプライマリキーを持つオブジェクト を add
しようとすると、落ちます。
reason: 'Attempting to create an object of type 'User' with an existing primary key value '1'.'
なお、中に入ってるRealmオブジェクトは問題ない模様。
結局のところ、
素直に あらかじめ新規保存・更新処理でも大丈夫にしておく必要がある。
let realm = try! Realm()
try! realm.write {
realm.add(product, update: .modified) // <- これで落ちない
}
はい。
これで「差分・新規」両方の保存ができます。
これで「知らないうちに、たくさん同じRealmが追加されてた!!」ってケースはないですね。
プライマリキーの注意点
プライマリキーに設定した値は、変更できないので注意。
reason: 'Primary key can't be changed after an object is inserted.'
Realm用のクラス編集時の注意点
マイグレーション
最初のうちは、Realm用クラスを作り、これを使い続けるため問題ないかとおもいます。
しかし…
import RealmSwift
class User: Object {
@objc dynamic var id = ""
@objc dynamic var name = ""
@objc dynamic var nickname = "" // <-新しく追加した
@objc dynamic var biography = ""
@objc dynamic var productsNum = 0
}
class Product: Object {
@objc dynamic var id = ""
@objc dynamic var title = ""
@objc dynamic var itemDescription = ""
@objc dynamic var createdAt = Date()
@objc dynamic var likesNum = 0
@objc dynamic var creator: User?
}
let realm = try! Realm() // <- このまま Realm を呼び出すと、落ちる。
エラーコード
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=10 "Migration is required due to the following errors:
- Property 'User.nikname' has been added." UserInfo={NSLocalizedDescription=Migration is required due to the following errors:
- Property 'User.nikname' has been added., Error Code=10}
リリースした後など、 Realm用クラスに、変数を増やすなどの変更を加えたりした場合、
次にアプリを起動した際、既に保存してあるデータとの型違いで 落ちます よね。
これは厄介ですね。
そういった場合はRealmを使用する前に 「マイグレーション」 という処理を行いましょう。
Realm に保存してあるデータを、最新のクラスに合わせます。
let CurrentSchemaVersion = 1 // 現在の SchemaVersion を、どこかに管理しておくといい。
let config = Realm.Configuration(schemaVersion: CurrentSchemaVersion, migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < CurrentSchemaVersion) {
// 必要に応じて、パラメーターを操作。
// 変数を追加するだけなら、中の処理は不要
// version 1
if oldSchemaVersion < 1 {
// 今回の例では、
// Userクラスに "name" があれば、"nickname"にその値を入れる。
migration.enumerateObjects(ofType: User.className()) { oldObject, newObject in
if let name = oldObject?["name"] as? String {
newObject!["nickname"] = name
}
}
}
}
})
Realm.Configuration.defaultConfiguration = config
// これでマイグレーション終了。
// Realm を呼び出しても、エラー落ちしない。
let realm = try! Realm()
oldObject
…変更前のオブジェクト。ここからデータを参照する。
newObject
…変更後のオブジェクト。変更したい値があれば、この中に入れる。
後からプライマリキーを追加した場合
既に使っていた値をプライマリキーとして登録した場合、
Realmに保存してあるデータが 既に重複していたら、落ちます。
error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=1 "Primary key property 'User.id' has duplicate values after migration."
こうなっては仕方ありません。マイグレーション を行う時に、プライマリキーを固有の値に変更しましょう。
マイグレーションでそのデータ自体を削除する方法が見当たらないので、消したい場合は削除用のIDを振って、後に別の処理で削除したほうが良いでしょう。
いい方法があったらオシエテネ
ということでね
本記事では書いていないことが、結構ありますね…
(他の書き方、クエリ検索条件、1対Nの場合… など)
わかりやすさを重視しましたが、いかがだったでしょうか…?
というか、zenn ってこういう使い方で合ってるっけ…
初見の人でも、イメージだけでも掴めていただければ…!!
長々と書きました。
ここまで貴重なお時間を割き、お読み下さいまして誠にありがとうございました!!
Discussion