I have recently announced starting to use Realm for my iOS projects. Then, I had just started using the technology, but didn’t really reach shipping stage until now. When it’s time to ship, that is when every ugly detail surfaces.
Note: This post is sponsored by fastlane! I found time to blog while it is busy building my app and generating screenshots.
For starters, I’ll be going over my use cases for using Realm, and then study each use case separately.
The first use case is using Realm to store app settings (preferences), and sharing them across all bundles (Main app, extension, AppleWatch app). This was supremely handled well by Realm, and I probably can’t be happier with the setup.
Application settings change very easily and quickly. Adding more settings in the future won’t be a problem at all thanks to Realm’s clean Migration APIs. Breaking the settings down into small tables also helps a lot in managing migrations and changes, so keep that in mind.
For starters, all you really need to do is set the
schemaVersion property on your
Configuration object to
First step is to set up an application group, and get a shared container going. The shared container will host the Realm files, and as per the docs, they can be accessed concurrently across processes without issues!
It is extremely important to have a simple API to read and update the settings, as well as extend the settings later. I probably have never had the pleasure of working with a cleaner API before, Realm takes the cake here as well.
To minimize duplicate code and stream line settings tables, though, I had to leverage Swift protocol extensions to roll out some convenient accessors.
First, let’s see how my current settings code looks like: Note: I use capital letters for static accessors
That is all there is to it! There is actually a lot of hidden power behind this concise syntax, so let’s dig deeper.
First, you should be asking: Where is
CurrentSettings class variable coming from? Also, same thing goes to
Aha! Here is where Swift protocol extensions come into play. The common settings functionality is grouped into the protocol extension below:
Now if you followed that properly, you’ll realize that any new settings table can easily get all these goodies by simply conforming to
SettingsTable and implementing the
To wrap up this section about Simple API, I’ll just say that it was quite important to break my application settings into smaller parts and pieces. This allows Realm tables to evolve independently, therefor giving us granular control over each settings group.
Nothing to be said here about performance, really. We have a single object per table, and about 5 tables total. Everything ran fast enough for me to just ignore benchmarking this.
To take this a step further, notifications should work across processes. I am not too worried or keen about taking care of that right now, so meh.
Key Value Store
The second use case for Realm was to be used as a key-value cache for downloads. The key type is
String, however, the value is a JSON serialized
NSData blob. Finally, we add an auto-updating
NSDate() there to maintain the cache timestamp. This use case is also neatly covered by Realm + Swift generics.
If we take a look at the Realm part of this sub-system, it is summed up in a few lines of code:
As for Swift generics, it’s pretty lengthy an can’t be fully covered in this article. A quick skim, however, is in order. The wrapper is just two classes:
Wraps the cache by providing an ability to instantiate a network task that automatically writes its result to the Realm cache.
Facilitates the data that describes a
Task. It is typically a URL which we sync the data from, a notification key which gets fired when the cache data is updated, and finally an expiration interval
Here is an example of how a
Task is created:
You may notice:
Task<[AnyObject]>: This decides how the network response is parsed
1.hour.timeInterval: This is just a convenience time library I use
Performance isn’t a big issue here. Since network operations are asynchronous by nature, we simply chain them with an asynchronous background access to the cache. Same thing goes to the deserialization.
My final use case was to ship static data with my apps. I had two different types of static data:
- Small number of “sounds” shipped with the app (39 total)
- Large number of “prayer times” for all the cities in Kuwait (26k)
Manipulating Static Realm Files
Dealing with static data was a huge pain, in general, due to the lack of a interpreter interface. Every time I needed to manipulate the data, I had to write full-fledged swift apps to parse and manipulate realm files.
Once you do that, you’ll quickly realize it is no fun. Realm files are very rigid, and quickly throw errors when the schema changes, objects are copied between realms, and possibly the most annoying is dropping existing tables (not possible).
I swallowed my pride for a good of two days, and dished out two realm files. The sounds realm file worked to date without any real issues. However, the humongous prayer times file wasn’t snappy at all. Performance became a bottleneck.
Thankfully, my data was easy to break down into smaller parts, so that’s what I did. Each city’s prayer times was moved to a separate realm file, then there was a single realm file with all the cities. Once I did that, a realm file would have 2196 objects, and that brought the performance back up again.
As for the API, I was easily able to write a nice wrapper around Realm to make the queries really easy and simple. Here is an example of how the
City objects are queried:
The main issue here is that I duplicate the
CityTable class, and create a light-weight
City struct that is returned to the caller. However, by doing that, I can now essentially switch out my core static data provider anytime I like without worrying about app wide changes. Also, I can control Realm’s complexity, since changing these Realm objects is a no-no (it’s a read-only realm!).
Realm is definitely better than any other persistence option, IMO. The only worry is performance. If you need that, you might as well roll out your own in memory map and use that.
Also, Fastlane has finished running a while ago, so time to get back to work!