-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Expand file tree
/
Copy pathRealmCollectionImpl.swift
More file actions
242 lines (223 loc) · 10.2 KB
/
Copy pathRealmCollectionImpl.swift
File metadata and controls
242 lines (223 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
////////////////////////////////////////////////////////////////////////////
//
// 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.
@usableFromInline
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 compiler(<6)
@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
actor.invokeIsolated(block, change)
}
}
}
#else
@available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *)
public func observe<A: Actor>(
keyPaths: [String]?, on actor: A,
_isolation: isolated (any Actor)? = #isolation,
_ 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
actor.invokeIsolated(block, change)
}
}
}
#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! }
}
// `with(object, on: actor) { object, actor in ... }` hands the object over
// to the given actor and then invokes the callback within the actor.
#if compiler(<6)
@available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *)
@_unsafeInheritExecutor
internal func with<A: Actor, Value: ThreadConfined>(
_ value: Value, on actor: A,
_ block: @Sendable @escaping (isolated A, Value) async throws -> NotificationToken
) async rethrows -> NotificationToken {
if value.realm == nil {
fatalError("Change notifications are only supported for managed objects")
}
let tsr = ThreadSafeReference(to: value)
let config = Unchecked(wrappedValue: value.realm!.rlmRealm.configurationSharingSchema())
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
}
// This is safe but 5.10's sendability checking can't prove it
// nonisolated(unsafe) can't be applied to a let in guard so we need
// a second variable
nonisolated(unsafe) let v = value
return try await block(actor, v)
} ?? NotificationToken()
}
#else
@available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *)
internal func with<A: Actor, Value: ThreadConfined>(
_ value: Value,
on actor: A,
_isolation: isolated (any Actor)? = #isolation,
_ block: @Sendable @escaping (isolated A, Value) async throws -> NotificationToken?
) async rethrows -> NotificationToken {
if value.realm == nil {
fatalError("Change notifications are only supported for managed objects")
}
let tsr = ThreadSafeReference(to: value)
nonisolated(unsafe) let config = value.realm!.rlmRealm.configurationSharingSchema()
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, confinedTo: scheduler))
guard let value = tsr.resolve(in: realm) else {
return nil
}
return try await block(actor, value)
} ?? NotificationToken()
}
#endif