Realm Cocoa 3.0 (including both our Objective-C and Swift APIs) is one of our biggest major-version releases. It addresses a list of issues, improves coherency across the API, and adds a list of new and exciting features to the Realm Database and the Realm Platform.
If you are curious to read more about all the changes in 3.0, you can head to GitHub where we do development in the open and keep a pretty detailed release change log.
One of the most requested features from developers has been the ability to store simple values in Realm’s List
class. That’s one of these features that is seemingly easy to implement but turns out to require quite the finesse to implement just right…
No fear! I’m glad to report that 3.0 brings, what we call, lists of primitives to the Realm Database.
What’s a list of primitives all about?
Previously the List
class could store a collection of realm objects (i.e. sub-classes of the Object
class). What many developers have asked for was to be able to make simple lists of numbers or strings. The only solution pre 3.0 was to create a new type which has a simple number or string property and use that object to “wrap” that primitive type.
This solution did work well but it did require some extra work on the developer side and it didn’t let us do any optimizations under the hood.
Meet the new and shiny List
class! It has all features that developers already love but this time it can hold objects, value types, optionals, and more!
Here’s the full list of types you can now store in a List
: Bool
, Int
, Int8
, Int16
, Int32
, Int64
, Float
, Double
, String
, Data
,
and Date
(and their optional versions).
So if you want to store a list of words in one of your objects, let’s say it’s a blog post with tags, you do it like this:
class BlogPost: Object {
@objc var title = ""
let tags = List<String>()
convenience init(title: String, tag: String) {
self.init()
self.title = title
self.tags.append(tag)
}
}
The BlogPost
class features a title
and a list of tags
of type String
. The convenience initializer already shows how to add values to the tags
list, but here’s also how you would create a new object and push some more tags:
let post = BlogPost(title: "New List class!", tag: "RealmSwift")
post.tags.append("technology")
post.tags.append("swift")
When you print the object to the console you will see something along the lines of:
BlogPost {
title = New List class!;
tags = RLMArray<string> <0x608000114c70> (
[0] RealmSwift,
[1] technology,
[2] swift
);
}
Should you for some reason need to also have nil
values in your list, you can make the List
storage type an Optional
like so:
class BlogPost: Object {
@objc var title = ""
let tags = List<String?>()
...
}
Use cases
Now that we’ve seen how to use the new and shiny List
class, let’s go beyond the basics and look into when you might want to use some of its new features.
Cross-file object references
Let’s look at a Realm schema which stores a list of authors and articles. As the user uses the app they can favorite some of the articles or the authors. For this setup you might build a schema like the following:
class Author: Object { ... }
class Article: Object {
@objc var id = UUID().uuidString
@objc var title = ""
@objc var author: Author?
override static func primaryKey() -> String? {
return "id"
}
}
class Favorites: Object {
let articles = List<Article>()
let authors = List<Author>()
}
You define Author
and Article
and then an extra type called Favorites
(of which you are going to always keep a single object in your Realm) which contains lists of articles and authors. Each time the user favorites something you can just add a reference to the object in the corresponding list.
And this is quite convenient since at any time you can reach out inside the list of favorites and grab the value of any property on any object like so:
print(realm.objects(Favorites.self).first!.articles[0].title) // prints the title
But what about the case when Author
and Article
are objects stored in a read-only synced file so you need to have a separate schema to store the user’s Favorites
, either locally on disk or even synchronized to a different Realm server?
In that case instead of storing references to the objects you will need to store their unique ids and you will split the schema in two. One file to hold the synced objects:
//synced realm schema
class Author: Object { ... }
class Article: Object {
@objc dynamic var id = UUID().uuidString
@objc dynamic var title = ""
@objc dynamic var author: Author?
override static func primaryKey() -> String? {
return "id"
}
}
And a second schema to hold the favorites (notice the change of the lists’ type):
class Favorites: Object {
let articles = List<String>()
let authors = List<String>()
}
So now you can grab the ids of all locally favorited articles and then fetch the matching Article
objects from your synced file:
// get the favorite ids
let idList = localRealm.objects(Favorites.self).first!.articles
// get the matching objects
let favoriteArticles = syncedRealm.objects(Article.self)
.filter("id IN %@", idList)
print(favoriteArticles)
I hope that example has already given you some great ideas how to improve your own code! Now let’s look at a slightly different example.
Streams of data
For this quick example let’s assume you’re building an app which tracks the location of the device and synchronizes the location data in real-time to a server. When offline, the app buffers the last 50 locations. The stream is FIFO - the app adds coordinates at the end of the list and the server consumes the oldest ones first.
With the newly available in List
features you can conveniently store latitude and longitude in two separate lists like so:
class LocationStream: Object {
let lats = List<Double>()
let lngs = List<Double>()
}
As said, a List
can contain a number of types so while we’re looking at storing location data right now the current example is widely relevant to any app that reads a device sensor and needs to sync the readings up to a server.
Next you can add a util method to the LocationStream
class like so:
func append(location: CLLocation) {
guard let _ = realm else { fatalError("Can't use this one detached object") }
lats.append(location.coordinate.latitude)
lngs.append(location.coordinate.longitude)
if (lats.count > 50) {
lats.removeFirst(lats.count - 50)
}
if (lngs.count > 50) {
lngs.removeFirst(lngs.count - 50)
}
}
List
performs all the heavy work under its hood of storing and managing the data, while you’re left with simple and familiar methods to do use like count
, remove
, removeFirst
, append
and others - much like you’re used with Swift’s built-in Array
type.
The difference of course being that any changes to List
are reflected on disk or synchronized to a server depending on your setup.
Finally here’s how you’d receive location data in your CLLocationManagerDelegate
and sync it to your server (or buffer it in case the device is offline):
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let stream = realm.objects(LocationStream.self).first!
try! realm.write {
for l in locations {
stream.append(location: l)
}
}
}
In this case of course the heavy lifting of storing and syncing data in real time is done by the Realm Platform. But List
did offer a handy way to quickly store some numbers and easily manage them without polluting your schema with wrapper types. And that’s great!
What’s next for List enthusiasts?
I hope you liked the new superpowers of List
and are busy at work to simplify some of your own Swift code by using lists of primitives.
If you’re interested in learning what are some of the other changes in Realm Cocoa 3.0 head to the release notes on GitHub.
And as always, if this sweet sweet talk about seamlessly synchronizing data between mobile apps and/or a server sparked your curiosity do read more about the Realm Platform!
About the content
This content has been published here with the express permission of the author.