Edited at

【Swift4】Realm+Codableを使ったお手軽なDB Part.4(番外編)


はじめに

前回の記事【Swift4】Realm+Codableを使ったお手軽なDB Part.3(クエリ編)の続きです、この記事がラストです!

今回もCodable関係ありませんが、Realmを使用する上でのTipsをまとめました。


マイグレーション

テーブルの追加や、データ構造の変更を行った場合には、Realmのマイグレーションを行う必要があります。

Realmのインスタンスを取得する際に、指定するConfigrationにスキーマのバージョンを指定します。

こうすることで、Realmがバージョンを判断して、マイグレーションを行ってくれます。

let config = Realm.Configuration(

schemaVersion: <realmSchemaVersion>,
migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < <realmOldSchemaVersion> {}
})
let realm = try! Realm(configuration: config)

テーブルの追加やカラムの追加であれば、Migrationブロック内で特に行う作業はありません😇

開発初期など頻繁に変更が起こるような場合では、マイグレーションを行うのではなく、新しくデータベースファイルを作り直してしまうことも出来ます。

let config = Realm.Configuration(deleteRealmIfMigrationNeeded: true)

let realm = try! Realm(configuration: config)


コンパクション

トランザクションの激しい場合など、実際のデータよりRealmのデータベースのファイルサイズが大きくなってしまう事があります。自動的にファイルサイズが小さくなることが無いため、明示的に使用していない部分を開放し、データベースのファイルサイズを小さくする操作(コンパクション)が場合によっては必要です。


現状のRealmのアーキテクチャではファイルサイズは増加するのみで減少することはありません。パフォーマンスと並列処理の安全性を両立するためです。

さらに、ファイルサイズを拡張させるのはコストの高いシステムコールを使用するので、それが頻繁に呼ばれることを避けるためにRealmは実行時にファイルサイズを縮小することはしません。その代わり、空き領域は自動的に再利用されます。


let config = Realm.Configuration(shouldCompactOnLaunch: { totalBytes, usedBytes in

// totalBytesはディスク上のRealmファイルの容量を示します。 (使用領域 + 空き領域)
// usedBytesは実際に使用している領域の容量を示します。

// ファイルサイズが100MB以上、かつ使用領域が全体の50%より少ない場合にコンパクションを実行
let oneHundredMB = 100 * 1024 * 1024
return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
})
do {
// Realm is compacted on the first open if the configuration block conditions were met.
let realm = try Realm(configuration: config)
} catch {
// handle error compacting or opening Realm
}

(Realm ObjC & Swift 2.6: 非同期にRealmを開くオプション・起動時に自動コンパクションの追加、不具合の修正より)


バックグラウンドでのRealmアクセス

Realmは、インスタンスを取得したスレッドと同じスレッドでないと動作しません。Mainスレッドでアクセスを行っても、高速に動作してくれるので大抵の操作は問題ありません。それでも、時間のかかる操作ではバックグラウンドで書き込み操作を行ってほしいですよね?

Realmの公式ドキュメントでは、

// Query and update from any thread

DispatchQueue(label: "background").async {
autoreleasepool {
let realm = try! Realm()
let theDog = realm.objects(Dog.self).filter("age == 1").first
try! realm.write {
theDog!.age = 3
}
}
}

このように、DispatchQueue.asyncを使用することで、バックグラウンドで書き込むことが出来ます。

しかし、Realmを呼ぶたびにDispatchQueue.asyncautoreleasepoolブロックで囲むのは面倒ですし、ネストも深くなってしまうのでコードが見づらくなってしまいます。

そこで、Realm 簡単にバックグラウンドスレッドで書き込みするExtensionを、Swift4に対応させてExtensionを追加しました。


Realm+.swift

extension Realm {

public var queue: OperationQueue {
get {
if let queue = objc_getAssociatedObject(self, &StoredProperties.queue) as? OperationQueue {
return queue
}

let queue = self.createDefaultQueue()
self.queue = queue
return queue
}
set {
objc_setAssociatedObject(self, &StoredProperties.queue, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}

public func writeBackground(block: @escaping (_ store: Realm) -> Void, completion: @escaping () -> Void) {

self.queue.addOperation {
autoreleasepool {
do {
let realm = try Realm(configuration: self.configuration)
try realm.write {
block(realm)
}
} catch {
// FIXME: Error Handling
}
OperationQueue.main.addOperation {
completion()
}
}
}
}

private enum StoredProperties {
static var queue: Void?
}

private func createDefaultQueue() -> OperationQueue {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = QualityOfService.default
return queue
}
}


このコードを使うと、以下のように使うことが出来ます。Realmの通常の操作と変わらない操作感で、バックグラウンドのアクセスを行うことが出来るようになります!😃

realm.writeBackground(block: { realm in

// update
}, completion: {
// completion block
})


さいごに

Realm+Codableシリーズとして、この記事を含め4件の記事を書きました!

自分の備忘録も兼ねているので、コメント、質問どんどんお待ちしています😊


株式会社Nexceed にて、一緒に働いてくれる仲間を募集中です:point_down::point_down::point_down:

- 建築業界に革命を!!AI × BIMの世界を作り出すアプリエンジニア募集!