Live Objects

In iOS, LiveObjects are represented by the EkoObject and EkoCollection classes. EkoObject represents live updates on one model, while EkoCollection represents live updates on a collection of models.


Observing live changes to any EkoObject can be done via a simple observe block:

let token: EkoNotificationToken? = client.user?.observe { userObject, _ in
guard let user: EkoUser = userObject.object else { return }
print("Current user display name: \(user.displayName)")

In this example the block observes the data of the currently authenticated user and prints out the displayName. The observe block can be called multiple times throughout the lifetime of the application (as long as its associated EkoNotificationToken is retained in memory):

  • If the requested object data is stored locally on the device, the block will be called immediately with the local version of the data (this can be verified this through the EkoObject property dataStatus).

  • In parallel, a network request for the latest version of the data is fired, once the network returns the data, the observe block will be called again with the updated data.

  • Any future changes to the data (whenever the user changes its displayName on another device, for example) can trigger additional callbacks.

Any UI update logic can be performed within the observe blocks, which will always be called in the main thread: this way, you can ensure that your UI displays the most up-to-date version of the state at all times.

In case you'd like to operate exclusively with fresh data, without using the potientially out-of-date local data, you can make sure to do so by reading the EkoObject dataStatus property, which reflects the status of the callback data, and check that its value is set to fresh.

You can also use the EkoObject loadingStatus property to determine the current state of network operations being performed by the LiveObject. This is useful for any UI element that needs to communicate the loading state.

The EkoNotificationToken is a token that is used to control the lifecycle of a LiveObject. The observeWithBlock callback will be triggered only if the notificationToken is retained in memory: once it is released, the observe block will no longer be called, and its memory will be automatically released. Hold a strong reference to any EkoNotificationToken whose block you would like still to be called.

Beside the observeWithBlock method, a observeOnceWithBlock method is also available, which will trigger its callback only once, regardless of the dataStatus of the callback and the associated token retainment (the token still needs to be retained to fire once).

To stop receiving observeWithBlock updates, release the associated EkoNotificationToken or call invalidate method on the token itself.


An EkoCollection instance is given for any queries that return a list of objects. EkoCollection has the same change observation interface as EkoObject, but contains a few other helper methods around data access that allows better integration with collection views like UITableView.

Data Access

Unlike most databases, EkoCollection does not return all data in an array. Instead, data are fetched one-by-one using the objectAtIndex: method. This allows the framework to store most of the actual result on disk, and load them in memory only when absoutely necessary. Additionally, the objectAtIndex: API fits perfectly into UITableView's tableView:cellForRowAtIndexPath:. Together with the count property, a typical tableView integration looks like this:

override func viewDidLoad() {
let messageRepository = EkoMessageRepository(client: client)
messagesCollection = messageRepository.messages(withChannelId: channelId, reverse: true)
messageToken = messagesCollection.observe { [weak self] _, _, _ in
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Int(messagesCollection.count())
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// since we want to display the newest message at the last row of the table view, we need to flip the indexes order.
let messageCount: UInt = messagesCollection.count()
let row = messageCount - UInt(indexPath.row + 1);
if let message = messagesCollection.object(at: UInt(row)) {
// build cell
return cell


EkoCollection offers both a nextPage and previousPage methods which will trigger a local cache lookup, a network call, and multiple LiveObject updates once new data is returned. After the method call is successful, the number of records returned by EkoCollection will be increased by the additional number of new records. For the typical use case of infinite scroll, you can call nextPage directly in the scroll view delegate method:

override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Check if user is scrolling to the next page
// isScrollingToNextPage = .....
if isScrollingToNextPage {

Lastly, if there is a need to shrink the list of models exposed back to original first page, when passing the EkoCollection object to a new view for example, you can do so by calling resetPage into the collection itself.