Realm Notifications on Background Threads with Swift

If you’re using Realm on iOS you’ve probably seen at least once this exception in Xcode’s console: “Can only add notification blocks from within runloops”. Some developers might guess that notifications work only on the main thread and just move on.

Actually Realm supports delivering notifications on any thread that has a running runloop. The main thread has one by default, but you can add a runloop on any thread you create.

In this short article we’ll see how to quickly write a helper class to facilitate creating new threads and installing a runloop on them so that you can observe for Realm notifications in the background.

Spinning a new thread & adding a runloop

Threads are one of Apple’s technologies allowing to run code concurrently in a single application. Abstractions like operations and Grand Central Dispatch allow a somewhat easier development of asynchronous code by introducing task-oriented concurrency, but under the hood those still use threads.

If you’d like to read a bit more on Apple’s threading model, you can check their online documentation.

Realm uses Apple’s run loops to provide notifications and those are tied directly to threads as each thread can have at most one run loop running. Therefore to use notifications in the background you’ll need to do that on your own background thread.

Get more development news like this

Luckily Apple’s Thread class makes that really easy. You can create a new “vanilla” thread using the default Thread() init or you can start a thread off with a closure like so:

let myThread = Thread {
  // ... some code to get that party started ...
}
myThread.start()

You create a new thread and its first task is to execute the code in that closure parameter.

New threads are created without a runloop and that’s why you get the exception mentioned at the beginning of the article when you use GCD queues with Realm.

RunLoop is an Apple-provided class that facilitates event handling - for example running timers, checking sources for input, and more. Naturally Realm uses it as well to deliver its own notifications.

If you’d like to learn more about run loops on the Cocoa platforms, check out the online documentation.

The RunLoop class makes it really easy to add a new run loop to a thread. Access the RunLoop.current property and that will either return the current thread’s existing run loop or create a new one. Finally call the run() method to start the loop.

There are few more details if we were to create a real background worker class, so let’s have a look at a fully-fledged code example.

Background notifications - a primer

In this section of the article we’ll look at building a naïve image cache. The finished demo allows you to add new File object from any thread to the app’s Realm file and a background worker will pick this object up and cache the referenced image locally.

Generic background worker

Let’s start by creating a generic background worker class, which takes a closure and runs it on a newly created thread.

In this example we’ll use the code we looked into earlier to:

  • create a new thread and start it off with some closure
  • use that closure to install and run a run loop on the new thread
  • perform code on the new thread
class BackgroundWorker: NSObject {
  private var thread: Thread!
  private var block: (()->Void)!

  internal func runBlock() { block() }

  internal func start(_ block: @escaping () -> Void) {
    self.block = block

    let threadName = String(describing: self)
      .components(separatedBy: .punctuationCharacters)[1]

    thread = Thread { [weak self] in
      while (self != nil && !self!.thread.isCancelled) {
        RunLoop.current.run(
          mode: RunLoopMode.defaultRunLoopMode, 
          before: Date.distantPast)
      }
      Thread.exit()
    }
    thread.name = "\(threadName)-\(UUID().uuidString)"
    thread.start()

    perform(#selector(runBlock),
      on: thread,
      with: nil,
      waitUntilDone: false,
      modes: [RunLoopMode.defaultRunLoopMode.rawValue])
  }

  public func stop() {
    thread.cancel()
  }
}

BackgroundWorker is as generic as they get. The start(_) method creates a new thread, gives it an identifying name (handy when you’re debugging your code), and starts the thread. Finally, it performs its runBlock() allowing you to run your own initialization code right after the thread is started.

Let’s look at the closure we pass to Thread(block:): it runs a while loop and before each iteration it checks if the current worker hasn’t been released and if the thread hasn’t been cancelled. Once either of these two conditions is met the while loop ends and destroys the thread.

This class allows you to easily create a new thread by just passing any closure to the start() method. Additionally, we add a stop() method to be able to cancel the thread.

Now let’s see how to make a concrete specialization of BackgroundWorker for our image cache.

Background image cache

First of all let’s define the File Realm object which will serve the cache:

class File: Object {
  convenience init(_ url: String) {
    self.init()
    remoteUrl = url
  }

  dynamic var remoteUrl = ""
  dynamic var localUrl  = ""
}

We will be adding new File objects with just their remoteUrl property set and our background worker will download the images to disk and update the corresponding localUrl property:

class CacheWorker: BackgroundWorker {
  private var token: NotificationToken?

  override init() {
    super.init()
    start { [weak self] in
      let realm = try! Realm()
      let files = realm.objects(File.self)
        .filter("localUrl = ''")
        
      self?.token = files.observe { changes in
        switch changes {
        case .update(_, _, let insertions, _):
          for index in insertions {
            print("download: \(files[index].remoteUrl)")
            print("[main thread: \(Thread.isMainThread)]")
            // download from web ...
            // update files[index].localUrl = "file:///..."
          }
        default: break
        }
      }
    }
  }

  deinit {
    token?.invalidate()
  }
}

CacheWorker inherits from BackgroundWorker and adds a default init. When you create an instance, CacheWorker calls its parent’s start(_) and sets a closure to run in a new thread - the closure observes all File objects that have an empty localUrl property (e.g. the images that haven’t been downloaded yet).

For debugging purpose the code also prints to the console when a new File has been inserted and also if the notification is being process on the main thread.

Using it in an app

Finally, let’s test the class in an iOS app:

class AppDelegate: UIResponder, UIApplicationDelegate {
  let cache = CacheWorker()

  ...
}

class SomeAPI {
  ...
  let realm = try! Realm()
  try! realm.write {
    realm.add(File("http://rx-marin.com/images/avatar.jpg"))
  }
  ...
}

Once that new File object is successfully added to the app’s realm file, CacheWorker receives an insertion notification and prints out in the console:

download: http://rx-marin.com/images/avatar.jpg
[main thread: false]

Few of the perks of having your own custom class to manage your thread include that fancy name you gave your thread so you can easily identify it during debugging:

Background thread with name

and also being able to access Realm results or objects at a later point on the same thread.

Outro

Like in our naïve cache example there are viable use cases for observing for and reacting to realm notifications in the background.

Especially if you are using the Realm Platform new objects and data changes might be syncing down from your server at any time, so being able to react to the new data in the background without blocking your UI is essential.

Have questions or comments? Reach us at https://twitter.com/realm !

Next Up: Learn more about fine-grained notifications in Realm

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