Realm List's New Superpowers

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.

Get more development news like this

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!

Next Up: New Features in Realm Obj-C & Swift

General link arrow white

About the content

This content has been published here with the express permission of the author.


Marin Todorov

Marin Todorov is an independent iOS consultant and publisher. He’s co-author on the book “RxSwift: Reactive programming with Swift” the author of “iOS Animations by Tutorials”. He’s part of Realm and raywenderlich.com. Besides crafting code, Marin also enjoys blogging, writing books, teaching, and speaking. He sometimes open sources his code. He walked the way to Santiago.

4 design patterns for a RESTless mobile integration »

close