Skip to content

Instantly share code, notes, and snippets.

@k-o-d-e-n
Created October 18, 2017 20:27
Show Gist options
  • Save k-o-d-e-n/dfa3afdfa34e60b93b48fb375f4a48bf to your computer and use it in GitHub Desktop.
Save k-o-d-e-n/dfa3afdfa34e60b93b48fb375f4a48bf to your computer and use it in GitHub Desktop.
Extended Realm Collection
import Foundation
import RealmSwift
extension AnyRealmCollection {
var asArray: [Element] {
return map { $0 }
}
}
final class ExtendedRealmCollection<O: Object>: RealmCollection {
typealias SortComparison = (O, O) -> ComparisonResult
typealias Token = Int
fileprivate let source: AnyRealmCollection<O>?
var filters: NSPredicate?
fileprivate var sorters: [SortComparison] = []
private var notificationToken: NotificationToken?
private var nextToken = Int.min
private var subscriptions: [Token: (ExtendedRealmCollection<O>) -> Void] = [:]
private var hasLocalFilters: Bool {
return filters != nil
}
private var hasLocalSorters: Bool {
return sorters.count > 0
}
var elements: ExtendedRealmCollection<O> { return self }
var results: [O] = []
var localModification: ((O) -> Void)?
var hasLocalFiltersOrSorters: Bool {
return hasLocalFilters || hasLocalSorters
}
static func empty() -> ExtendedRealmCollection<O> {
return ExtendedRealmCollection<O>()
}
fileprivate init() {
source = nil
}
init<T: RealmCollection>(source: T) where T.Element == O {
self.source = AnyRealmCollection(source)
notificationToken = addNotification { [weak self] (err) in
guard let `self` = self, err == nil else { return }
self.updateResults()
self.subscriptions.forEach { $0.value(self) }
}
updateResults()
}
deinit {
if let token = notificationToken {
token.stop()
}
subscriptions.removeAll()
}
func add(sorter: @escaping SortComparison) {
sorters.append(sorter)
updateResults()
}
func add(subscription: @escaping (ExtendedRealmCollection<O>) -> Void) -> Token {
defer { nextToken += 1 }
subscriptions[nextToken] = subscription
return nextToken
}
func remove(subscription with: Token) {
subscriptions.removeValue(forKey: with)
}
func removeSorters() {
sorters.removeAll()
}
func updateResults() {
guard let source = self.source else {
results = []
return
}
results = source.asArray
if let modification = localModification {
results.forEach(modification)
}
if hasLocalFilters {
results = results.filter { filters!.evaluate(with: $0) }
}
results = sortedIfNeededArray(results)
}
// MARK: Collection Protocols
typealias Element = O
typealias SubSequence = ExtendedRealmCollection<O>
typealias Index = Int
typealias Iterator = AnyIterator<O>
typealias Indices = DefaultRandomAccessIndices<ExtendedRealmCollection>
func makeIterator() -> AnyIterator<O> {
if hasLocalFiltersOrSorters {
return AnyIterator(results.makeIterator())
} else {
return source.map { AnyIterator($0.makeIterator()) } ?? AnyIterator { nil }
}
}
var startIndex: Int { return Swift.max(hasLocalFilters ? results.startIndex : source?.startIndex ?? 0, limitedBounds.lowerBound) }
var endIndex: Int { return Swift.min(hasLocalFilters ? results.endIndex : source?.endIndex ?? 0, limitedBounds.upperBound) }
func index(after i: Int) -> Int { return hasLocalFilters ? results.index(after: i) : source?.index(after: i) ?? 0 }
func index(before i: Int) -> Int { return hasLocalFilters ? results.index(before: i) : source?.index(before: i) ?? 0 }
subscript (position: Index) -> O { return hasLocalFiltersOrSorters ? results[position] : source![position] }
private var limitedBounds: Range<Int> = Range(uncheckedBounds: (Int.min, Int.max))
subscript(bounds: Range<Int>) -> ExtendedRealmCollection<O> {
guard let source = self.source else { return ExtendedRealmCollection.empty() }
let limited = ExtendedRealmCollection(source: source)
limited.limitedBounds = bounds
return limited
}
// MARK: Other
private func sortedIfNeededArray(_ array: [O]) -> [O] {
guard !hasLocalSorters else {
return array.sorted { o1, o2 in
for sorter in sorters {
let result = sorter(o1, o2)
if result == .orderedSame { break }
return result == .orderedAscending
}
return true
}
}
return array
}
var description: String {
return source?.description ?? "" + (filters?.description ?? "") + sorters.description
}
// MARK: ThreadConfined
var isInvalidated: Bool {
return source?.isInvalidated ?? true
}
var realm: Realm? {
return source?.realm
}
// MARK: RealmCollection
func index(of object: O) -> Int? {
return hasLocalFiltersOrSorters ? results.index(of: object) : source?.index(of: object)
}
func index(matching predicate: NSPredicate) -> Int? {
precondition(!hasLocalFilters, "Collection has local filters, please add new filter predicate to this array or find index in results handle")
return source?.index(matching: predicate)
}
func index(matching predicateFormat: String, _ args: Any...) -> Int? {
precondition(!hasLocalFilters, "Collection has local filters, please add new filter predicate to this array or find index in results handle")
return source?.index(matching: predicateFormat, args)
}
// MARK: Filtering
func filter(_ predicateFormat: String, _ args: Any...) -> Results<O> {
precondition(!hasLocalFilters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.filter(predicateFormat, args)
}
func filter(_ predicate: NSPredicate) -> Results<O> {
precondition(!hasLocalFilters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.filter(predicate)
}
// MARK: Sorting
func sorted(byKeyPath keyPath: String, ascending: Bool) -> Results<O> {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.sorted(byKeyPath: keyPath, ascending: ascending)
}
@available(*, deprecated, renamed: "sorted(byKeyPath:ascending:)")
func sorted(byProperty property: String, ascending: Bool) -> Results<O> {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.sorted(byProperty: property, ascending: ascending)
}
func sorted<S: Sequence>(by sortDescriptors: S) -> Results<O> where S.Iterator.Element == SortDescriptor {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.sorted(by: sortDescriptors)
}
// MARK: Aggregate Operations
func min<U: MinMaxType>(ofProperty property: String) -> U? {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.min(ofProperty: property)
}
func max<U: MinMaxType>(ofProperty property: String) -> U? {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.max(ofProperty: property)
}
func sum<U: AddableType>(ofProperty property: String) -> U {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.sum(ofProperty: property)
}
func average<U: AddableType>(ofProperty property: String) -> U? {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters, please add new filter predicate to this array or filter results handle")
return source!.average(ofProperty: property)
}
// MARK: Key-Value Coding
func value(forKey key: String) -> Any? {
return value(forKeyPath: key)
}
func value(forKeyPath keyPath: String) -> Any? {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters, please use closure for change values")
guard let values = source?.value(forKeyPath: keyPath) as? [Any] else { return nil }
return values
}
func setValue(_ value: Any?, forKey key: String) {
precondition(!hasLocalFilters, "Collection has local filters, please use closure for change value")
source?.setValue(value, forKey: key)
}
// MARK: Notifications
func addNotification(_ block: @escaping (Error?) -> Void) -> NotificationToken {
return source!.addNotificationBlock { (changes) in
switch changes {
case .initial: break
// block(nil)
case .update:
block(nil)
case .error(let error):
block(error)
break
}
}
}
func addNotificationBlock(_ block: @escaping (RealmCollectionChange<ExtendedRealmCollection<O>>) -> Void) -> NotificationToken {
precondition(!hasLocalFiltersOrSorters, "Collection has local filters or sorters, please use closure for change value")
return source!.addNotificationBlock { [unowned self] changes in
switch changes {
case .initial:
block(.initial(self))
case .update(_, deletions: let deleted, insertions: let inserted, modifications: let modified):
block(.update(self, deletions: deleted, insertions: inserted, modifications: modified))
case .error(let error):
block(.error(error))
break
}
}
}
func _addNotificationBlock(_ block: @escaping (RealmCollectionChange<AnyRealmCollection<O>>) -> Void) -> NotificationToken {
return source!._addNotificationBlock(block)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment