読者です 読者をやめる 読者になる 読者になる

A Day In The Life

とあるプログラマの備忘録

Realmを前提としたリポジトリパターンの実装

いろんなところで Realm が良いと聞くようになり最近やっと Realm を使い始めました。実際 Realm を使ってみると予想していたよりも使い勝手がよく Core Data には戻れそうにないです…。ソースコードは Swift3 に対応しています。

リポジトリパターンの実装

Core Data に比べてお作法的なものが少ないとはいえ、いくつかのお決まりのパターンがあるので DDD のリポジトリパターンを適用してみました。クリーンアーキテクチャをまったく意識してないお気軽実装です。

import Foundation
import RealmSwift

class Repository<DomainType: Object> {
    
  var realm: Realm
    
  init() {
    self.realm = try! Realm()
  }
  // PK検索
  func find(primaryKey: String) -> DomainType? {
    return realm.objects(DomainType.self).filter("id == %@", primaryKey).first
  }
  // 全部取ってくる
  func findAll() -> [DomainType] {
    return realm.objects(DomainType.self).map({$0})
  }
  // 条件指定
  func find(predicate: NSPredicate) -> Results<DomainType> {
    return realm.objects(DomainType.self).filter(predicate)
  }
  // データ追加と更新
  func add(domains: [DomainType]) {
    try! realm.write {
      realm.add(domains, update: true)
    }
  }
  // データ削除
  func delete(domains: [DomainType]) {
    try! realm.write {
      realm.delete(domains)
    }
  }
  // トランザクション
  func transaction(_ transactionBlock: () -> Void) {
    try! realm.write {
      transactionBlock()
    }
  }
  // スレッドをまたいだオブジェクトの解決
  func resolve<Confined: ThreadConfined>(_ reference: ThreadSafeReference<Confined>) -> Confined? {
    return self.realm.resolve(reference)
  }
}

一つのリポジトリオブジェクトが一つの Realm オブジェクトを保持するようにしました。リポジトリオブジェクトをスレッドをまたいで使わない想定です。

リポジトリクラスの使い方

以下の Person クラスを例にリポジトリクラスの使い方をみてみましょう。

class Person: Object {
  dynamic var id = UUID().uuidString
  dynamic var name = ""
  // プライマリーキーを設定
  override static func primaryKey() -> String? {
    return "id"
  }
}

Person オブジェクトの生成から削除まで一通りやってみます。

// パーソンオブジェクト生成
let glassonion = Person()
glassonion.name = "glassonion"
let jude = Person()
jude.name = "jude"
// パーソンリポジトリ生成
let repo = Repository<Person>()
// データ追加
repo.add(domains: [glassonion, jude])
// 全件取得
let allPeople = repo.findAll()
// 条件を指定して取得
let predicate = NSPredicate(format: "name CONTAINS %@", "gl")
let people = repo.find(predicate: predicate)
// データの更新
repo.transaction {
  allPeople[0].name = "hoge"
}
// 削除
repo.delete(domains: allPeople)

スレッドをまたいだモデルオブジェクトのあつかいかた

スレッドをまたいでデータの更新をする場合は以下のようにします。

let repo = Repository<Person>()
let person = repo.findAll().first!
// スレッドセーフなオブジェクトに変換
let personRef = ThreadSafeReference(to: person)
// スレッド生成
DispatchQueue(label: "io.realm.RealmTasks.bg").async {
  // 新たにリポジトリ生成
  let repo = Repository<Person>()
  // スレッドの違うリポジトリと関連付ける
  if let person = repo.resolve(personRef)!
  // データ更新
  repo.transaction {
    person.name = "Michelle"
  }
}

参考記事

参考書籍