In this five-episode series, we’re going to have a look at how Realm facilitates building reactive applications from the ground up. In the third episode, we’ll use Inboxly’s sample project again, and learn how we can use Realm to make our apps more resilient.
You can find the other episodes here:
- Episode 1 - Introduction to Realm
- Episode 2 - Responsiveness
- Episode 4 - Elasticity
- Episode 5 - Message Driven
Introduction (0:00)
Hi, and welcome back to Building Reactive Apps with Realm. As we’ve seen in previous episodes, reactive code exhibits a number of characteristics, regardless of whether you are using an actual reactive code framework, or features built into the iOS SDK.
In this series, we’ve been looking at how Realm facilitates designing and developing reactive apps. In this episode, we are going to have a quick look at how Realm allows you to make your apps more resilient. A couple of examples we are going to look into are keeping your app as functional as possible without network connection, and the way that Realm ensures you stay on your happy pad as you read or write objects on disk.
In the previous episode, we looked at a small sample project called Inboxly, and you’ve seen how easy it is to separate concerns in your app. The network and data controllers in the app work in the background and add new objects to your Realm while your view controllers operate on the main thread, and are only concerned about reacting to Realm notifications and refreshing the UI. Since your view controller doesn’t know anything about your networking or data controllers, in the case network is down, or unreachable, that doesn’t affect viewing and browsing data in any way.
Let’s have a look at the way that Realm’s write transactions make your app more resilient.
Write Transactions (1:26)
Realm ensures as little friction as possible when you are writing to your data storage. To understand why Realm’s write transactions are rock solid, let’s have a look at how they work. In this diagram, the pink Realm block represents your Realm file from where your view controllers would read data. When you open a write transaction, a copy of your data is made specifically for that write transaction and you do your changes to that copy.
After your transaction is over and the file is correctly persisted on the desk, it becomes the file where all other parts of your app read from. The old file is discarded. These write transactions happen serially, or one after the other. Each transaction starts by making a copy of the latest successfully updated file, and therefore, you never end up working on a corrupted, or partially updated file.
Next, we are going to dive into a short example how error handling will look like for Realm writes in your app.
Demo (2:32)
Let’s poke a little bit at our guest project, Inboxly. It’s a simple messaging app with a few tabs that show messages as they come from the server down to the app. In our feed view, for example, we have the FeedTableViewController
, which reads messages from Realm and shows them on screen. It’s only concerned with reading messages.
The place where all of the messages that show up in the table view are being added to the Realm is the DataController
. It’s a very simplistic approach. The Data View Controller periodically runs every few seconds, and then calls the simulated API that gives back an array of JSON objects. Here they are being added to the Realm:
@objc private func fetch(timer: NSTIMER) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {[weak self] in
self?api.getMessages {jsonObject in
let newMessages = jsonObject,map { Message(value: $0) }
let realm = try! Realm(configuration: RealmConfig.Main.configuration)
let me = realm.objects(User.self).first!
try! realm.write {
me.messages.insertContentsOf(newMessages, at: 0)
}
print("saved \(newMessages.count) messages")
}
})
}
As you can see here, we use a try!
to let the exceptions be un-caught, but let’s think of what can we do with this code to make it a little bit safer.
As you saw, there is no way to experience any errors when you are changing your own changes into Realm. In other words, if you are using the API correctly, then you will not experience any errors.
For example, if you are trying to create a new object with the same ID as an object that already exists, and you have marked in your model that this ID must be unique, then you will still get an exception. That will let you know that you are using the Realm API incorrectly, meaning you are intentionally trying to create the same ID, even though you said this ID is going to be unique.
You can only get an exception if the device disk space is over. As every other file operation that you’re performing, your device might be out of free space, so that opening a write transaction, for example, is not possible. Or, if the disk is failing, then you will get an exception as well if there is a hardware failure.
When you are opening Realm, when you are saying try! Realm(configuration...
, if you’re opening a Realm that is encrypted, and you’re not providing the same password as you used to encrypt the data, then you also get an exception. What we are going to do to recover from this failure, or insufficient free space, will be to wrap this in a do/catch
block:
@objc private func fetch(timer: NSTIMER) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {[weak self] in
self?api.getMessages {jsonObject in
let newMessages = jsonObject,map { Message(value: $0) }
do {
let realm = try! Realm(configuration: RealmConfig.Main.configuration)
let me = realm.objects(User.self).first!
try! realm.write {
me.messages.insertContentsOf(newMessages, at: 0)
}
} catch {
}
print("saved \(newMessages.count) messages")
}
})
}
All of this happens in the background. There is no view controller that owns this code. What we’re going to do in here is, first of all, remove the try
exclamation marks, so that we can do some error handling.
For the catch
block, it’s up to you to decide what to do, but since my data controller is running in the background and merging JSON as it comes from the server, if the disk space is over, there’s not much I can do. I cannot store these JSON objects in their raw form in another file.
I think that the only viable option in here would be to use the notification center to post a notification, and I will send the error as the object:
@objc private func fetch(timer: NSTIMER) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {[weak self] in
self?api.getMessages {jsonObject in
let newMessages = jsonObject,map { Message(value: $0) }
do {
let realm = try Realm(configuration: RealmConfig.Main.configuration)
let me = realm.objects(User.self).first!
try realm.write {
me.messages.insertContentsOf(newMessages, at: 0)
}
} catch let e as NSError {
NSNotificationCenter.defaultCenter().postNotificationName("RealWriteFailed"), object: e)
}
print("saved \(newMessages.count) messages")
}
})
}
Conclusion (6:22)
That’s the most of what we can do here. I will have probably a logger that will listen for the notification:
NSNotificationCenter.defaultCenter().postNotificationName("RealWriteFailed"), object: e)
Or I will have some UI element that is listening for that notification and show a banner, or some alert, on top of what the user is seeing right now. That way they will know either they’re failing, or there is insufficient free space.
Receive news and updates from Realm straight to your inbox