Partial Sync for Swift

Before, apps needed to sync a full realm in order to give you the full benefits of our mobile platform. Now with Partial Sync, we can observe both objects and queries and sync them on demand without downloading the entire realm! Now it's possible to build an entire enterprise ready app with only one realm! If you are happy with the multi-realm model you can still use partial syncing as well!

Benefits of partial sync:

  • Avoid dealing with complex multiple realm models
  • Efficiently stream down and open syncing data lazily without a burden of bandwidth or local storage limitations

  • Object Level Permissions for fine grained control

  • Robust Permissions for advanced server-side control

Constructing a Partially Synced Realm

All you have to do is pass in partial: true in the SyncConfiguration struct!

let realm = try! Realm(configuration:
    Realm.Configuration(
        syncConfiguration: 
            Realm.SyncConfiguration(
                url: URL(string: "realm://localhost:9080")!,
                partial: true
            )
    )
)

Result<Object> is always what is on disk

No matter what you are doing with Realm, whether you are syncing, not syncing, online or offline... if you have a reference to Result<T: Object> you are always referencing what is on the disc. It is the only state that you can introspect from your own application. Syncing is a whole separate operation that only mutates objects that fall within the representation of that result.

Prefer Notification APIs over to Synchronous APIs:

The core idea of Partial Sync is the ability to lazily sync queries. Thus, everything works better in an asynchronous fashion even though Realm can efficiently retrieve items on disc. We highly encourage that your code uses our asynchronous notificationBlock based APIs whenever possible.

How do I only sync a subset of a realm?

We have a similar API called addNotificationBlock that returns a NotificationToken

Syncing will begin when you observe an object or aResults<T:Object>

Observing via our notification blocks ensures that syncing takes place! All our notification blocks return a token

let cars = realm.filter(Car.self, "name == 'Toyota'")
let token = cars.addNotificationBlock { changeEvent in
    //this will fire for mutations on the query
}

If the token is deallocated, then the addNotificationBlock will stop firing and syncing will stop.

What you have to know about Synchronous Queries?

Of course you can use Synchronous querying. We aren't saying that they are even a bad practice. There are certainly many situations where they are preferable to the notification blocks. However you should understand what happens when you ask for data in a Synchronous vs. Notification methods.

Synchronous APIs must immediately return data. Because we can run into a state where the local data and the server data are not being synced, you may get confused as to why you might see data in the dashboard yet no data on your app. Unfortunately, we can't simply block your current thread, we must return you "something" immediately.

Imagine a situation where you have tons of cars in your Realm Object Server with the name "Toyota". However, a newly installed app pointing to your server attempts to do this:

let cars = realm.filter(Car.self, "name == 'Toyota'")
print("there are \(cars.count) cars")

The result can be empty! But this doesn't mean that it's empty on the server. The best way to make sure is to use the notification block.

How do I know if a notification block fired locally or remotely?

Since Realm Objects can change locally or from the server, both changes fire handlers in theaddNotificationBlocks . The new changeEvent has a property called remote?: RealmObjectServer which allows you to introspect if the server has responded with a change or not.

let cars = realm.filter(Car.self, "name == 'Toyota'")
let token = cars.addSyncNotificationBlock { changeEvent in
    if changeEvent.remote == nil {
        print("The change occurred locally!")
    } else {
        print("The change occurred from the server!")
    }
}

What's this new remote property on the changeEvent

This new optional property on the changeEvent is called remote.For most situations, you'll only want to check if it is or isn't nil. However for the advanced user, it's packed with rich information about the notifications from the server.

class Remote {
    syncUserId: String? // the user which made the change
    operation: Set<Realm.Operation> // a set of operational transforms applied to fire this block
    acceptedTimestamp: Date // a timestamp from when the server accepted the changes
    receivedTimestamp: Date // a timestamp from when the client applied these changes right before the notification block fired
    syncWorkerId: String?
}

Stopping Notification Blocks, Stopping Syncing, and Freeing up Space

Similar to the regular APIs, stopping a notification block is easy. All you have to do is call

token.stop()

By default calling stop will do 3 things in a partially synced realm:

  1. Stop further notifications to be fired on that notification block
  2. Stop syncing if there are no more identical results that are being synced across the application
  3. Purge the data from disk if there are no more identical results that are being synced across the application

To explicitly stop syncing, simply call notificationToken.stop(stopSyncing: true)

Depending on your application you may be interested in freeing up space with queries that you are no longer using. To free up disc space call notificationToken.stop(purgeLocal: true).It's important to understand the purgeLocal is not a delete operation that is sent out to the server. The result will be mutated if this called.

Realm Engineer Notes: By default, we have overloaded parameters on token.stop() that has both purgeLocal = true and stopSyncing = true

New API Methods and Properties

The only new API methods addNotificationBlockWithPrimaryKey and a new property on RealmSwift.Object called ObjectId

addNotificationBlockWithPrimaryKey

We've added a new method to observe a single object by it's primary key. Because partial syncing is all about lazy data loading, we need a way to asynchronous retrieve data all asynchronously. objectForPrimaryKey is the only method that lacks an asynchronous counterpart.

__objectId and methods

The __objectId is a completely unique id for any object that exists in realm. It's always generated by the internal SDK and can never be set but only read. Though this is unique across your ROS server, we allow you to reference it if you are interested. This doesn't stop you from defining your own primary keys!

class Person: Object {
    dynamic var socialSecurityNumber: String = ""
    static func primaryKey() -> String { 
        return "socialSecurityNumber"
    }
}

let p = Person()
print(p.__objectId)
print(p.socialSecurityNumber)

You can reference the __objectId in your own code and you can retrieve it with the following technique

let object: Object? = realm.objectWithObjectId(yourObjectId)
// observe it with
realm.addNotificationBlockForObjectId(yourObjectId) { object in
    print("Object with ObjectId")
}
// you can check the type of the object with guards and if let patterns
if let car = realm.objectWithObjectId(yourObjectId) as? Car { 
    //we retrieved a car with an objectId
}

Limiting

Rarely do we ever grab an entire view of a database. Slicing and sorting data is critical to data management. Along with our sorting of queries, we can also limit them.

let sortedDogs = realm
    .objects(Dog.self)
    .filter("color = 'tan' AND name BEGINSWITH 'B'")
    .sorted(byKeyPath: "name")
    .limit(20)

sortedDogs.addNotificationBlock { changeEvent in 
    let results = changeEvent.results
    print(results.count)
    // watch as the results change in order and count! They will never pass 20 items
}

Sorting and Limiting is Powerful, but with partial sync the best practice use the addNotificationBlock to see when objects move in and out of the criteria.

Excluding Relationships and Link

One of the beauties of realm is the ability to full realize a relational model. However there are some considerations for Partial Sync when using a heavily related design.

  1. By default, observing a notification block, will sync related objects deeply
  2. To prevent relationships from partial sync queries, you can exclude properties

Partial Sync's main benefit is efficient resource consumption. There are many cases where you are only interested in certain items from a query but not it's deep parent-child relationships. For example, you may have a used car buying app like:

class Car {
    dynamic var _id: String = UUID.string()
    dynamic var name: String = "Toyota"
    dynamic var price: Double = 0
    let imageUrls = List<String>()
    dynamic var condition: String = ""
    dynamic var discount: Double = 0

    let owners = List<User>()
}

class User {
    dynamic var _id: String = UUID.string()
    dynamic var name: String = ""
    let friend = List<User>()
}

In your first view controller, you'd like to purely show a list of cars, but you're not interested in showing the owners. There's no need to download more data than you need especially as your app grows in size.

Instead we can observe a query like so with the exclude:

let carsWithoutOwners = realm.filter(Car.self, "name == 'Toyota'").exclude(["owners"])
let token = cars.addNotificationBlock { changeEvent in
    // we are now observing cars, but any changes to any associated owner will not be fired here!
}

Realm Engineer Notes: In the future, we want to introduce a link type that represents a known link to the server but that is not synced locally. This would have a method to asynchronously resolve the link.

Easy! The default value will be returned, if it's supplied by your schema. If it is not an optional, this will throw an error. It's up to you to keep track between the excluded properties and the notification blocks. If you have our Realm.Sync.debug(.verbose) set, then you should see a console log warning you that you are accessing a property that's been excluded.

Fully test your code before going to production. Excluding properties is powerful but take care to avoid unexpected behavior by constructing your queries sensibly.

results matching ""

    No results matching ""