tarunon / RealmIO

RealmIO makes Realm operation more safely, reusable and composable by using reader monad.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RealmIO

Build Status codecov License: MIT

CocoaPods Carthage compatible

RealmIO makes Realm operation more safely, reusable and composable by using reader monad.

Motivation

Realm operations (especially write operations) is not reusable if you write a function as follows:

func addDog(name: String) throws {
    let realm = try Realm()
    try realm.write {
        let dog = Dog()
        dog.name = name
        realm.add(dog)
    }
}

At first glance, It works well, but actually there are some problems if you call this function multiple times.

addDog(name: "Taro")
addDog(name: "Jiro")
  • You cannot add 2 dog objects in a same transaction. In this case, realm.write is called twice.
  • Typically, to begin transaction is very slow, and realm.write locks realm instance. We should not call realm.write needlessly.

img img

You can also write this function as follows:

func addDog(name: String, to realm: Realm) {
    let dog = Dog()
    dog.name = name
    realm.add(dog)
}
try realm.write {
    addDog(name: "Taro", to: realm)
    addDog(name: "Jiro", to: realm)
}

2 addDog calls will be run in a same transaction, but user needs to call realm.write by oneself.

  • The user can not judge from the signature whether to begin a transaction by oneself.
  • It is sometimes painfully to pass Realm instance as argument explicitly.

Usage

Define Realm operation as RealmIO

RealmIO<RW, T> represents a realm operation.

  • RW is actually Read or Write. It represents that operation is readonly or not.
  • T is a return value type.

and you can also use RealmRead<T> and RealmWrite<T>, these are just alias of RealmIO<Read, T> and RealmIO<Write, T> .

public typealias RealmRead<T> = RealmIO<Read, T>

public typealias RealmWrite<T> = RealmIO<Write, T>

For example, operation that reads User object from realm is typed RealmRead<User>.

func find(by userID: Int) -> RealmRead<User> {
    ...
}

If you already know about reader monad, RealmIO<RW, T> is the same as Reader<Realm, T>, except for the RW type parameter.

Run Realm operation with realm.run(io:)

You can run preceding realm operation with realm.run(io:).

let io: RealmRead<User> = find(by: 123)
let result = try? realm.run(io: io)

If operation needs to write to realm (it means io is an instance of RealmWrite<T>), realm.run(io:) begins transaction automatically.

realm.run(io:) throws 2 error types.

  • Realm.Error
  • Error that thrown by user

Compose realm operation with flatMap

flatMap allows you to compose realm actions.

func add(dog: Dog) -> RealmWrite<Void> {
    ...
}

func add(cat: Cat) -> RealmWrite<Void> {
    ...
}

let io: RealmWrite<Void> = add(dog: myDog).flatMap { _ in add(cat: myCat) }

And you can run composed operation in a same transaction.

realm.run(io: io) // Add `myDog` and `myCat` in a same transaction.

RW type parameter of composed operation is determined by 2 operation types.

read.flatMap { _ in read }   // Read
read.flatMap { _ in write }  // Write
write.flatMap { _ in read }  // Write
write.flatMap { _ in write } // Write

Use convenient operator

Realm.IO provides useful operators to create RealmIO instance. See: Realm+Operator.swift

NOTE: Some of methods provided by RealmIO are not thread safe yet.

Some methods that takes Object as an argument such as Realm.IO.add, Realm.IO.delete are not thread safe for now. It is not better to pass Object directly. If you want to use this method safely, you should call realm.run(io:) in a same thread, or use with flatMap.

// OK: call `realm.run(io:)` in a same thread.
let io1 = Realm.IO.add(object)
try realm.run(io: io1)

// OK: use with `flatMap`
let io2 = Realm.IO.objects(Dog.self).flatMap(Realm.IO.delete)
try realm.run(io: io2)

Since ThreadSafeReference has a constraint that references can not be resolved within write transactions, implementation with ThreadSafeReference can not be done in 1.0. I'm considering measures after the next version.

Installation

CocoaPods

pod 'RealmIO'

Carthage

github "ukitaka/RealmIO"

Requirements

RealmIO uses swift 3.1 functionality, so support only swift 3.1 now.

  • Xcode 8.3
  • swift 3.1

RealmIO supports following platforms.

  • iOS 8.0+
  • macOS 10.10+
  • watchOS 2.0+
  • tvOS 9.0+

Credits

RealmIO was inspired by Slick's DBIOAction.

About

RealmIO makes Realm operation more safely, reusable and composable by using reader monad.

License:MIT License


Languages

Language:Swift 97.6%Language:Ruby 2.4%