The Realm SDK Enables Clean and Easy Separation of Concerns

Realm Database series header

This is the fourth post in a multi-part series on the Realm Database; it highlights some of the multi-platform aspects of the the database and the high reusability of the code across devices.

If you missed the first post on our custom engine, the second post on the benefits of some of the Realm API types, and the third post on sharing code across platforms, we recommend taking few minutes to read through.

Also make sure to check out the fifth post on Realm SDK features that allow developers to provide partial UI updates based on fine-grained notifications and the final and sixth post, which shows how the features in the previous parts come together to make a database for modern mobile apps.


A third-party SDK might empower you or get in your way. For example, a highly functional but monolithic library will allow you to quickly implement some basic app logic, but create more trouble than value down the road.

The Realm Database aims to not only allow you to persist your data, but to help you implement good practices. It does that by offering you a flexible SDK in a number of programming languages (Swift, Java, JavaScript, C#, and more) - it allows you to fine-tune your data objects and be in control all the way up to the higher-level APIs like reacting to changes and talking to your server.

Any SDK, third-party library, or service you’d like to base your own product on should exhibit the same qualities you aim to have in your own product.

Separation of Concerns

If you’ve read the previous posts in this series, you remember that we’ve prepared a little companion project to provide examples for the features highlighted in each article. You can always clone (or download) the project from its GitHub repository and peak inside.

Get more development news like this

Let’s take a quick look at how the Realm Swift SDK helps separate concerns in the companion project. We didn’t stick to a certain architecture since you might like a different one; this way you can read through the code and apply what you like best in your own project.

The project consist of a single Xcode workspace, which features one target for each of Apple’s four platforms: iOS, macOS, tvOS, and watchOS:

Demo Project Platforms

Since the Realm Swift SDK supports all four platforms, the project shares the data layer code between all of them. In the previous post we had a look at a data entity called Favorite and figured that it only depends on Foundation and RealmSwift frameworks:

import Foundation
import RealmSwift

class Favorite: Object {
    dynamic var symbol = "💖"
    var symbolIndex: Int? {
        return Favorite.symbols.index(of: symbol)
    }
    static let symbols = ["💖", "🚀", "🤕"]
    static let noSymbolIndex = -1
}

The data entity class can be, therefore, re-used as-is between platforms - it only makes use of common Foundation types and Realm itself.

But let’s move beyond the data entity classes.

In essence, all of the different apps that the project builds are list-based applications. They might look a bit different across platforms but the basic structure is:

  • a default screen showing a list of repositories, with the ability to filter them (no filtering in the watch app)
  • a single repository detail screen, with the ability (where available) to mark the selected repository as favorite

The default screen presents a list of items - the iOS project uses a UITableView, the macOS project an NSTableView, tvOS a UICollectionView, etc. Here’s how the storyboard looks for iOS:

List iOS App Storyboard

And here’s the watchOS app:

List watchOS App Storyboard

Regardless of the specific UI components each project makes use of, in the end you’d like to present a list, and therefore you can separate the “presenting a list of repositories” logic from the target-specific UI code.

This way the project ends up featuring three clean-cut layers:

1. Data storage and observation layer

Storing and retrieving data is handled by Realm - the API depends on Apple’s Foundation but no other framework, so your Object subclasses and/or other code that handles your entities can be easily abstracted away from your main project.

This layer also implements any specifics of your data model that need to be tightly integrated with the code talking straight to your Realm database.

For example the Repository object describes all the data properties the app persists in Realm:

class Repository: Object {
    dynamic var id: Int = 0
    dynamic var stars: Int = 0
    dynamic var url: String = ""
    ...
}

But it also features few simple business rules, such as getting all repositories from the Realm database:

static func all(searchTerm: String? = nil) -> Results<Repository> {
    let realm = try! Realm()
    return realm.objects(Repository.self)
        .filter("name contains[c] %@", searchTerm ?? "")
        .sorted(byProperty: "stars", ascending: false)
}

You can check out the companion project to see others, like toggling the repo’s “favorite” status.

2. Shared application and presentation logic

This layer implements application logic that can be abstracted away from specific platforms. We already mentioned “presenting a list of repos”, but this layer also handles “reacting to data model changes” and networking.

A ReposListPresenter class takes care of presenting a list of repositories. It talks to the data layer in order to load a list of repositories and keeps the list up to date.

It adopts a number of platform-specific protocols to be able to serve as a data source for all project targets. (For your favorite architecture, you might create a generic “data source” protocol and bridge it to the platform-specific ones.)

Since updating the actual UI includes plenty of platform-specific code, ReposListPresenter provides a generic callback to allow each target to handle UI updates in its own code:

func loadRepos(searchFor term: String? = nil, updated: @escaping (RealmCollectionChange<Results<Repository>>) -> Void) {
    refreshToken?.stop()
    repos = Repository.all(searchTerm: term)
    refreshToken = repos?.addNotificationBlock(updated)
}

loadRepos(searchFor:updated:) searches the Realm database for matching repositories, creates a notification subscription for the result set, and binds change notifications to the updated parameter closure.

3. Views and view controlling

Finally we have a look at the target-specific UI code.

Each target features its own storyboard, using the native controls for that platform. The view controller classes for each platform all use ReposListPresenter and implement their own UI update code in the callback.

For example, the macOS app uses the NSTableView API:

reposPresenter.loadRepos(searchFor: sender?.stringValue ?? "", updated: { changes in

    switch changes {
        case .initial:
            self.tableView.reloadData()
            
        case .update(_, let deletions, let insertions, let updates):
            self.tableView.beginUpdates()
            self.tableView.insertRows(at: IndexSet(insertions), withAnimation: .slideDown)
            self.tableView.reloadData(forRowIndexes: IndexSet(updates), columnIndexes: IndexSet(integer: 0))
            self.tableView.removeRows(at: IndexSet(deletions), withAnimation: .slideUp)
            self.tableView.endUpdates()

        default: break
    }

})

The NSTableView API allows for handling tables with multiple columns and features macOS-specific update animations.

The tvOS view controller, on the other hand, uses a collection view to display the repositories on a TV screen and therefore makes use of the UICollectionView API:

reposPresenter.loadRepos(searchFor: sender?.text) { changes in

    switch changes {
        case .initial:
            self.collectionView?.reloadData()

        case .update(_, let deletions, let insertions, let updates):
            let cv = self.collectionView!

            cv.performBatchUpdates({
                cv.insertItems(at: insertions.map {IndexPath(row: $0, section: 0)})
                cv.reloadItems(at: updates.map {IndexPath(row: $0, section: 0)})
                cv.deleteItems(at: deletions.map {IndexPath(row: $0, section: 0)})
            }, completion: nil)

        default: break
    }
}

This piece of code uses UICollectionView.performBatchUpdates(_:completion:) to reflect the data model changes in the UI. You can see that the code is similar in logic but different in syntax compared to the macOS code.

Since observing for changes and triggering the update closure are abstracted away, the current layer only cares about platform-specific UI code.

Reactive Apps by Default 🚀

You saw how easy it is to separate concerns in the demo project. As the app gets more complicated, the different layers can be abstracted further away into separate frameworks, and, potentially, be re-used in other projects as well.

It is important to notice that, even in its beginning stage, the project is already implementing a reactive architecture, thanks to the Realm SDK.

The app is message driven. For example, the ReposListPresenter only reacts to change notifications, while the GitHubAPI class focuses on networking and updating the repositories in the database. The two primary classes that read and write to the database work completely independently (and possibly on different threads), and rely solely on Realm’s notifications to trigger updates.

The app is resilient, as the Realm SDK is incredibly sturdy and allows you to easily avoid data persistence problems like merge conflicts or lost data. By allowing you to easily decouple classes, any issues you experience in a class that writes the data will not affect your presenter classes.

Finally the app is responsive, as it maintains a local cache of repositories in its Realm database. As soon as you open the app you can browse and search the locally stored repositories while a fresh list is being grabbed in the background. As soon as the updated data is received and stored on disc, Realm delivers change notifications and the app updates all relevant UI by animating items in or out.

Last but not least, since the Realm SDK has already turned the project into a reactive app, you can always build further upon this foundation (if you need that). If you’d like to use a third-party reactive framework, you can easily integrate it on top of the current project code - for example RxSwift for iOS and RxJava in your Android project.

We Hope You Like What You’re Seeing!

In this post, you learned about some of the ways that the Realm SDK empowers good architecture. The SDK is powerful and very flexible, so no matter which particular architecture is your favorite you’ll be able to separate concerns, decouple classes, and be sure your data is safe.

As always, if you have few extra minutes, you are welcome to check out the Realm Database:

In the next installment in this series, we’re going to look in more detail into Realm’s fine-grained notifications, and why you don’t need a fetched results controller when using a modern database.

See you next time! 👋

Next Up: What makes Realm different #5: Live Objects and Fine-Grained Notifications

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