iosswiftrealmreactive-swift

How to observe objects count on realm db


I want to observe the object count of the realm DB. I wrote this approach:

let realm = try? Realm()
self.notificationToken = realm?.objects(AnalyticsEventDto.self).observe { _ in
    if let count = realm?.objects(AnalyticsEventDto.self).count {
        observer.send(value: count)
    }
}

There is another way to do so? Thanks


Solution

  • I would use the RealmCollectionChange parameter rather than creating a new Results object in the closure. This also lets you handle the error:

    let realm = try? Realm()
    self.notificationToken = realm?.objects(AnalyticsEventDto.self).observe { change in
        switch change {
        case .initial(let results):
            observer.send(value: results.count)
        case .update(let results, deletions: _, insertions: _, modifications: _):
            observer.send(value: results.count)
        case .error(let error):
            observer.send(error: error)
        }
    }
    

    But a more ReactiveSwifty way of doing this might be to wrap the entire Realm collection notification API to make it reactive. This is a bit involved, but it is more general and potentially usable in multiple places in your code:

    /// We want to send errors via the `ReactiveSwift` error event, so create a new `CollectionChange` enum that
    /// mimics `RealmCollectionChange` except it doesn't have an `error` case.
    enum CollectionChange<CollectionType> {
        case initial(CollectionType)
        case update(CollectionType, deletions: [Int], insertions: [Int], modifications: [Int])
    }
    
    /// Extending `ReactiveExtensionsProvider` and `Reactive` is the standard way to add reactive extensions
    /// to existing types in a way that avoids name collisions.
    extension Results: ReactiveExtensionsProvider { }
    
    extension Reactive where Base: RealmCollection {
        /// This wraps Realm collection notifications in a very general reactive way.
        private func changeNotificationsRaw() -> SignalProducer<RealmCollectionChange<Base>, Never> {
            return SignalProducer { [base = self.base] observer, lifetime in
                let token = base.observe { change in
                    observer.send(value: change)
                }
    
                lifetime.observeEnded {
                    token.invalidate()
                }
            }
        }
    
        /// This just maps `RealmCollectionChange` to our own `CollectionChange` type while translating errors
        /// to ReactiveSwift error events.
        func changeNotifications() -> SignalProducer<CollectionChange<Base>, Error> {
            return changeNotificationsRaw().flatMap(.concat) { realmChange -> SignalProducer<CollectionChange<Base>, Error> in
                switch realmChange {
                case .initial(let collection):
                    return SignalProducer(value: .initial(collection))
                case let .update(collection, deletions, insertions, modifications):
                    return SignalProducer(value: .update(collection, deletions: deletions, insertions: insertions, modifications: modifications))
                case .error(let err):
                    return SignalProducer(error: err)
                }
            }
        }
    }
    

    You could then use it like this:

    realm.objects(AnalyticsEventDto.self).reactive.changeNotifications()
        .map { change -> Int in
            switch change {
            case .initial(let results):
                return results.count
            case .update(let results, _, _, _):
                return results.count
            }
        }
        .take(during: self.lifetime)
        .start { event in
            print(event)
        }