//////////////////////////////////////////////////////////////////////////// // // Copyright 2021 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// import Foundation import Realm // RealmCollectionImpl implements all of the RealmCollection protocol except for // description and single-element subscript. description actually varies between // the collection wrappers, and Sequence infers its associated types from subscript, // so moving that here requires defining those explicitly in each collection. // // The functions don't need to be documented here because Xcode/DocC inherit // the documentation from the RealmCollection protocol definition, and jazzy // excludes this file entirely. internal protocol RealmCollectionImpl: RealmCollection where Index == Int, SubSequence == Slice<Self>, Iterator == RLMIterator<Element> { var collection: RLMCollection { get } init(collection: RLMCollection) } extension RealmCollectionImpl { public var realm: Realm? { collection.realm.map(Realm.init) } public var isInvalidated: Bool { collection.isInvalidated } public var count: Int { Int(collection.count) } public subscript(bounds: Range<Self.Index>) -> SubSequence { return SubSequence(base: self, bounds: bounds) } public var first: Element? { return collection.firstObject!().map(staticBridgeCast) } public var last: Element? { return collection.lastObject!().map(staticBridgeCast) } public func objects(at indexes: IndexSet) -> [Element] { guard let r = collection.objects!(at: indexes) else { throwRealmException("Indexes for collection are out of bounds.") } return r.map(staticBridgeCast) } public func index(of object: Element) -> Int? { if let indexOf = collection.index(of:) { return notFoundToNil(index: indexOf(staticBridgeCast(fromSwift: object) as AnyObject)) } fatalError("Collection does not support index(of:)") } public func index(matching predicate: NSPredicate) -> Int? { if let indexMatching = collection.indexOfObject(with:) { return notFoundToNil(index: indexMatching(predicate)) } fatalError("Collection does not support index(matching:)") } public func filter(_ predicate: NSPredicate) -> Results<Element> { return Results<Element>(collection.objects(with: predicate)) } public func sorted<S: Sequence>(by sortDescriptors: S) -> Results<Element> where S.Iterator.Element == SortDescriptor { return Results<Element>(collection.sortedResults(using: sortDescriptors.map { $0.rlmSortDescriptorValue })) } public func distinct<S: Sequence>(by keyPaths: S) -> Results<Element> where S.Iterator.Element == String { return Results<Element>(collection.distinctResults(usingKeyPaths: Array(keyPaths))) } public func min<T: _HasPersistedType>(ofProperty property: String) -> T? where T.PersistedType: MinMaxType { return collection.min(ofProperty: property).map(staticBridgeCast) } public func max<T: _HasPersistedType>(ofProperty property: String) -> T? where T.PersistedType: MinMaxType { return collection.max(ofProperty: property).map(staticBridgeCast) } public func sum<T: _HasPersistedType>(ofProperty property: String) -> T where T.PersistedType: AddableType { return staticBridgeCast(fromObjectiveC: collection.sum(ofProperty: property)) } public func average<T: _HasPersistedType>(ofProperty property: String) -> T? where T.PersistedType: AddableType { return collection.average(ofProperty: property).map(staticBridgeCast) } public func value(forKey key: String) -> Any? { return collection.value(forKey: key) } public func value(forKeyPath keyPath: String) -> Any? { return collection.value(forKeyPath: keyPath) } public func setValue(_ value: Any?, forKey key: String) { return collection.setValue(value, forKey: key) } public func observe(keyPaths: [String]?, on queue: DispatchQueue?, _ block: @escaping (RealmCollectionChange<Self>) -> Void) -> NotificationToken { // We want to pass the same object instance to the change callback each time. // If the callback is being called on the source thread the instance should // be `self`, but if it's on a different thread it needs to be a new Swift // wrapper for the obj-c type, which we'll construct the first time the // callback is called. var col: Self? func wrapped(collection: RLMCollection?, change: RLMCollectionChange?, error: Error?) { if col == nil, let collection = collection { col = self.collection === collection ? self : Self(collection: collection) } block(.init(value: col, change: change, error: error)) } return collection.addNotificationBlock(wrapped, keyPaths: keyPaths, queue: queue) } #if swift(>=5.8) @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor public func observe<A: Actor>( keyPaths: [String]?, on actor: A, _ block: @Sendable @escaping (isolated A, RealmCollectionChange<Self>) -> Void ) async -> NotificationToken { await with(self, on: actor) { actor, collection in collection.observe(keyPaths: keyPaths, on: nil) { change in assumeOnActorExecutor(actor) { actor in block(actor, change) } } } ?? NotificationToken() } #endif public var isFrozen: Bool { return collection.isFrozen } public func freeze() -> Self { return Self(collection: collection.freeze()) } public func thaw() -> Self? { return Self(collection: collection.thaw()) } public func sectioned<Key: _Persistable>(sortDescriptors: [SortDescriptor], _ keyBlock: @escaping ((Element) -> Key)) -> SectionedResults<Key, Element> { if sortDescriptors.isEmpty { throwRealmException("There must be at least one SortDescriptor when using SectionedResults.") } let sectionedResults = collection.sectionedResults(using: sortDescriptors.map(ObjectiveCSupport.convert)) { value in return keyBlock(Element._rlmFromObjc(value)!)._rlmObjcValue as? RLMValue } return SectionedResults(rlmSectionedResult: sectionedResults) } } // A helper protocol which lets us check for Optional in where clauses public protocol OptionalProtocol { associatedtype Wrapped func _rlmInferWrappedType() -> Wrapped } extension Optional: OptionalProtocol { public func _rlmInferWrappedType() -> Wrapped { return self! } } #if swift(>=5.8) // `with(object, on: actor) { object, actor in ... }` hands the object over // to the given actor and then invokes the callback within the actor. // This might make sense to expose publicly. @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *) @_unsafeInheritExecutor internal func with<A: Actor, Value: ThreadConfined, Return: Sendable>( _ value: Value, on actor: A, _ block: @Sendable @escaping (isolated A, Value) async throws -> Return ) async rethrows -> Return? { if value.realm == nil { let unchecked = Unchecked(wrappedValue: value) return try await actor.invoke { actor in if !Task.isCancelled { return try await block(actor, unchecked.wrappedValue) } return nil } } let tsr = ThreadSafeReference(to: value) let config = Unchecked(wrappedValue: value.realm!.rlmRealm.configuration) return try await actor.invoke { actor in if Task.isCancelled { return nil } let scheduler = RLMScheduler.actor(actor, invoke: actor.invoke, verify: actor.verifier()) let realm = Realm(try! RLMRealm(configuration: config.wrappedValue, confinedTo: scheduler)) guard let value = tsr.resolve(in: realm) else { return nil } return try await block(actor, value) } } #endif