RealmIO
makes Realm
operation more safely, reusable and composable by using reader monad
.
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 callrealm.write
needlessly.
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.
RealmIO<RW, T>
represents a realm operation.
RW
is actuallyRead
orWrite
. 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.
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
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
Realm.IO
provides useful operators to create RealmIO
instance.
See: Realm+Operator.swift
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.
pod 'RealmIO'
github "ukitaka/RealmIO"
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+
RealmIO
was inspired by Slick's DBIOAction
.