おことわり
- 本稿で前提としている開発環境のバージョンは古いです。
- サンプルとして記載してるコードはSwiftらしくありません。
- 具体的には、安易に継承を使ってしまっていますが、Protocolを活用するほうが好ましいと思われます。
以上を踏まえつつ、お読みください。
はじめに
Realmとは
Version 1.0公開時(2016年5月)の公式ニュースより引用
https://realm.io/jp/news/realm-1.0/
RealmはSQLite上に構築されたいわゆるORMではありません。
私たちはモバイルアプリケーション開発者のために1からデータベースを開発しています。
そのため単なるキーバリューストアではなく、データベースエンジンが保持するデータに動的に対応付けられたネイティブなオブジェクトを提供します。
このことにより、Realmは簡潔なAPIとパフォーマンスの両立を達成しています。
Realmを用いることで、複雑なデータのモデリング、オブジェクト間のリンク、高度なクエリのすべてが可能となります。
Realm Swiftの資料
公式ドキュメント
https://realm.io/jp/docs/swift/latest/
とても充実していて、使い方については、これだけ読めば足りる感じ。
インストール手順もバッチリ記載されています。
使ってみた感想
私は目下、社内コミュニティで研究目的のiOSアプリを作成しており、そのアプリで使ってみました。
私はバリバリの業務系エンジニアで、iOSアプリは本業ではありません。
今まではOracleをメインにRDBMSしか使ったことがなかったので、オブジェクトのままデータを管理するという概念は目から鱗でした。
Realm Swiftを私なりに一言で表現するならば、「Swiftの、Swiftによる、Swiftのためのデータベース」。
Realm Swiftを利用することでコードが非常に簡潔になります。
「モバイルアプリケーション開発者のために1から」開発されたデータベース、という触れ込み通りと感じました。
サンプルコード
私はXcode + Swiftは初級者、Realmは初学者なので、イケてない部分もあるかと思います。
したがって、あくまで「一事例」として捉えていただければと思います。
コメントでのツッコミは歓迎です。
環境
Item | Version |
---|---|
Xcode | 7.3.1 |
Swift | 2.2 |
Realm Swift | 1.0.2 |
iOS | 9.3 |
Realmモデルクラス
今回のアプリで扱うデータ、請求と入金のモデル。
Realmでは、Objectクラスを継承する決まりになっています。
import Foundation
import RealmSwift
/// 請求モデルクラス
class ReceivableModel: Object {
dynamic var id: String = ""
dynamic var customerId: String = ""
dynamic var customerName: String = ""
dynamic var amount: String = ""
dynamic var settlementDate: String = ""
dynamic var note: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
import Foundation
import RealmSwift
/// 入金モデルクラス
class DepositModel: Object {
dynamic var id: String = ""
dynamic var sender: String = ""
dynamic var amount: String = ""
dynamic var depositDate: String = ""
dynamic var note: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
Data Access Object(DAO)
商用であれば将来的な拡張を踏まえてFactoryをかませると思いますが、今回は研究用アプリなので、そこまでは実装していません。
プロトコル
請求と入金のAccessorクラスはこのプロトコルで宣言されたメソッドを実装します。
モデルクラスは実装時に確定したいため、associatedtypeにより別名化しています。
Resultsクラスは、Realmの結果セットを格納するためのクラス(Realmコレクションクラス)です。
import Foundation
import RealmSwift
/// RealmデータベースへのAccessorはこのProtcolを実装する
protocol AccessorProtcol {
associatedtype ObjectType: Object
func getByID(id: String) -> ObjectType?
func getAll() -> Results<ObjectType>?
func set(data: Object) -> Bool
func delete(data: Object) -> Bool
}
Baseクラス
請求と入金のAccessorクラスの継承元です。
Baseを作った意図は以下の通りです。
モデルの定義変更時は、Realmのインスタンス作成前にマイグレーション・コードを書く必要があるようなので、Realmのインスタンス作成は一箇所に集約した方が良さそう。
Realmでは、異なるモデルであっても、追加/更新・削除のAPIインターフェイスが同一なので共通化できる。
import Foundation
import RealmSwift
/// RealmデータベースへのAccessorはこのclassを継承する。
class AccessorBase {
let realm: Realm
/// コンストラクタ
init() {
// Realmオブジェクト生成
realm = try! Realm()
}
/// データをUpdateする
/// - parameter data: データ
/// - returns: true: 成功
func set(data: Object) -> Bool {
do {
try realm.write {
realm.add(data, update: true) //プライマリキーで上書きする
}
return true
} catch {
print("\n--Error! AccessorBase#set")
}
return false
}
/// データをDeleteする
/// - parameter data: データ
/// - returns: true: 成功
func delete(data: Object) -> Bool {
do {
try realm.write {
realm.delete(data)
}
return true
} catch {
print("\n--Error! AccessorBase#delete")
}
return false
}
}
請求のAccessor
import Foundation
import RealmSwift
/// RealmデータベースへのAccessor: ReceivableModel
class ReceivablesAccessor: AccessorBase, AccessorProtcol {
/// Singleton
static let sharedInstance = ReceivablesAccessor()
private override init() {
super.init()
}
/// ID指定でデータを1件取得する
/// - parameter id: ID
/// - returns: ReceivableModel
func getByID(id: String) -> ReceivableModel? {
let models = super.realm.objects(ReceivableModel).filter("id = '\(id)'")
if models.count > 0 {
return models[0]
} else {
return nil
}
}
/// データを全件取得する
/// - returns: ReceivableModelのコレクション
func getAll() -> Results<ReceivableModel>? {
return super.realm.objects(ReceivableModel).sorted("id")
}
}
入金のAccessor
import Foundation
import RealmSwift
/// RealmデータベースへのAccessor: DepositModel
class DepositsAccessor: AccessorBase, AccessorProtcol {
/// Singleton
static let sharedInstance = DepositsAccessor()
private override init() {
super.init()
}
/// ID指定でデータを1件取得する
/// - parameter id: ID
/// - returns: DepositModel
func getByID(id: String) -> DepositModel? {
let models = super.realm.objects(DepositModel).filter("id = '\(id)'")
if models.count > 0 {
return models[0]
} else {
return nil
}
}
/// データを全件取得する
/// - returns: DepositModelのコレクション
func getAll() -> Results<DepositModel>? {
return realm.objects(DepositModel).sorted("id")
}
}
DAO呼び出し側
書き込み
...
let modelReceivable = ReceivableModel()
... //値のセットを行う
if !ReceivablesAccessor.sharedInstance.set(modelReceivable) {
return
}
...
UITableViewに請求データを表示
...
/// セルの数
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let receivables = ReceivablesAccessor.sharedInstance.getAll() else {
// データなし
return 0
}
return receivables.count
}
...
/// セルを設定
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let receivables = ReceivablesAccessor.sharedInstance.getAll() else {
// データなし
return UITableViewCell()
}
// indexのcellを設定
let receivable = receivables[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("ReceivableCell", forIndexPath: indexPath) as! ReceivableTableViewCell
cell.setData(receivable)
return cell
}
...
import UIKit
class ReceivableTableViewCell: UITableViewCell {
@IBOutlet weak var CustomerNameLabel: UILabel!
@IBOutlet weak var AmountLabel: UILabel!
@IBOutlet weak var SettlementDateLabel: UILabel!
@IBOutlet weak var NoteLabel: UILabel!
/// データを画面項目にセットする
/// - parameter model: データモデル
func setData(model: ReceivableModel) {
self.CustomerNameLabel.text = model.customerName
self.AmountLabel.text = model.amount
self.SettlementDateLabel.text = model.settlementDate
self.NoteLabel.text = model.note
}
}