In Bite #231, we took a look at Realm's new Fine-grained notifications functionality. Today we'll build upon this by checking out another new Realm feature: Queryable, Live Inverse Collections. Whew! That's a fancy name. This feature deserves one though, let's check it out.
Here's a Realm object with a relationship defined on it:
class Person: Object {
dynamic var name = ""
let dogs = List<Dog>()
}
That dogs
property can be used in a query, and it will even stay updated to reflect changes to the property's value made elsewhere in our app, automatically.
None of that is new though. What is new is the inverse of this mechanic.
Meet the all-new LinkingObjects:
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
let owners = LinkingObjects(
fromType: Person.self,
property: "dogs"
)
}
Here's what we get for defining things this way:
LinkingObjects are live and auto-updating. When new relationships are formed or removed, they will update to reflect the new state.
LinkingObjects can be used In queries, natively. (Previously this would need to be done in our code):
// People that have a child that have a parent named Diane.
realm.objects(Person).filter("ANY children.parents.name == 'Diane'")
// People whose parents have an average age of > 65.
realm.objects(Person).filter("parents.@avg.age > 65")
LinkingObjects behave like regular Realm collections:
// Which of my parents are over the age of 56?
self.parents.filter("age > 56")
// Calculate the age of my parents.
self.parents.average("age")
More info about Realm can be found at realm.io
We first looked at Realm way back in Bite #49. It's a great data storage solution for our mobile apps. Today we'll start looking at some of the latest improvements in Realm and the new capabilities they offer. First up is Fine-grained notifications. Let's dive in:
Realm has offered notifications of write operations for a while, they look like this:
let token = realm.addNotificationBlock { notif, realm in
// TODO: viewController.updateUI()
}
These are still around and work great, but it might help to know more about what changed. That's where the new Collection Notifications come in.
Collection notifications give us access the changes that just occurred at a fine-grained level, including the specific indices of insertions, deletions, etc
let results = try! Realm().objects(Spaceship).sorted("name")
let token = results.addNotificationBlock { (changes: RealmCollectionChange) in
// TODO: self.processChanges(changes)
}
changes
here is an enum that looks like this:
public enum RealmCollectionChange<T> {
case Initial(T)
case Update(T, deletions: [Int], insertions: [Int], modifications: [Int])
case Error(NSError)
}
.Update's
values can be easily mapped to NSIndexPath objects suitable for use in table views and collection views.
Here's a complete example showing all of this in action:
class SpaceshipsViewController: UITableViewController {
var notificationToken: NotificationToken? = nil
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
let results = realm.objects(Spaceships).filter("maxSpeed > 0")
// Observe Results Notifications
notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .Initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
break
case .Update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths(insertions.map { NSIndexPath(forRow: $0, inSection: 0) }, withRowAnimation: .Automatic)
tableView.deleteRowsAtIndexPaths(deletions.map { NSIndexPath(forRow: $0, inSection: 0) }, withRowAnimation: .Automatic)
tableView.reloadRowsAtIndexPaths(modifications.map { NSIndexPath(forRow: $0, inSection: 0) }, withRowAnimation: .Automatic)
tableView.endUpdates()
break
case .Error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
break
}
}
}
deinit {
notificationToken?.stop()
}
}
More info about Realm can be found at realm.io
Realm is a database made specifically for running on mobile devices. It works on Mac and iOS devices. (It even supports Android!) It's a great alternative to Core Data or even raw SQLite.
There's plenty to love about Realm, but the best part is it's ultra-simple API:
Define an Object
import RealmSwift
class Spaceship: Object {
dynamic var name = ""
dynamic var topSpeed = 0 // in km
}
Define a Related Object
class Spaceship: Object {
dynamic var name = ""
dynamic var topSpeed = 0
dynamic var owner: User?
}
class User: Object {
dynamic var firstName = ""
dynamic var lastName = ""
let spaceships = List<Spaceship>()
}
Create an Instance of an Object
var ship = Spaceship()
ship.name = "Outrider"
ship.topSpeed = 1150
var dash = User()
dash.firstName = "Dash"
dash.lastName = "Rendar"
ship.owner = dash
// need one Realm per thread
let realm = Realm()
realm.write {
realm.add(ship)
}
Find Objects
Queries in Realm couldn't be simpler. They are chainable. You can add as many calls to .filter
as you'd like. You can sort results using the chainable sorted
function.
realm
.objects(Spaceship)
.filter("topSpeed > 1000")
.filter("name BEGINSWITH 'O'")
.sorted("topSpeed", ascending: false)
Performance
Realm uses a "zero-copy" design that allows it process > 35 queries a second, as well as insert > 95,000 records a second in a single transaction.
More info about Realm can be found at realm.io.