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

A Day In The Life

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

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

ios swift 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"
  }
}

参考記事

参考書籍