-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Results.swift
431 lines (335 loc) · 16.4 KB
/
Results.swift
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 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
// MARK: MinMaxType
/**
Types of properties which can be used with the minimum and maximum value APIs.
- see: `min(ofProperty:)`, `max(ofProperty:)`
*/
public protocol MinMaxType {}
extension NSNumber: MinMaxType {}
extension Double: MinMaxType {}
extension Float: MinMaxType {}
extension Int: MinMaxType {}
extension Int8: MinMaxType {}
extension Int16: MinMaxType {}
extension Int32: MinMaxType {}
extension Int64: MinMaxType {}
extension Date: MinMaxType {}
extension NSDate: MinMaxType {}
// MARK: AddableType
/**
Types of properties which can be used with the sum and average value APIs.
- see: `sum(ofProperty:)`, `average(ofProperty:)`
*/
public protocol AddableType {
/// :nodoc:
init()
}
extension NSNumber: AddableType {}
extension Double: AddableType {}
extension Float: AddableType {}
extension Int: AddableType {}
extension Int8: AddableType {}
extension Int16: AddableType {}
extension Int32: AddableType {}
extension Int64: AddableType {}
/**
`Results` is an auto-updating container type in Realm returned from object queries.
`Results` can be queried with the same predicates as `List<Element>`, and you can
chain queries to further filter query results.
`Results` always reflect the current state of the Realm on the current thread, including during write transactions on
the current thread. The one exception to this is when using `for...in` enumeration, which will always enumerate over
the objects which matched the query when the enumeration is begun, even if some of them are deleted or modified to be
excluded by the filter during the enumeration.
`Results` are lazily evaluated the first time they are accessed; they only run queries when the result of the query is
requested. This means that chaining several temporary `Results` to sort and filter your data does not perform any
unnecessary work processing the intermediate state.
Once the results have been evaluated or a notification block has been added, the results are eagerly kept up-to-date,
with the work done to keep them up-to-date done on a background thread whenever possible.
Results instances cannot be directly instantiated.
*/
public struct Results<Element: RealmCollectionValue>: Equatable {
internal let rlmResults: RLMResults<AnyObject>
/// A human-readable description of the objects represented by the results.
public var description: String {
return RLMDescriptionWithMaxDepth("Results", rlmResults, RLMDescriptionMaxDepth)
}
/// The type of the objects described by the results.
public typealias ElementType = Element
// MARK: Properties
/// The Realm which manages this results. Note that this property will never return `nil`.
public var realm: Realm? { return Realm(rlmResults.realm) }
/**
Indicates if the results are no longer valid.
The results becomes invalid if `invalidate()` is called on the containing `realm`. An invalidated results can be
accessed, but will always be empty.
*/
public var isInvalidated: Bool { return rlmResults.isInvalidated }
/// The number of objects in the results.
public var count: Int { return Int(rlmResults.count) }
// MARK: Initializers
internal init(_ rlmResults: RLMResults<AnyObject>) {
self.rlmResults = rlmResults
}
// MARK: Index Retrieval
/**
Returns the index of the given object in the results, or `nil` if the object is not present.
*/
public func index(of object: Element) -> Int? {
return notFoundToNil(index: rlmResults.index(of: object as AnyObject))
}
/**
Returns the index of the first object matching the predicate, or `nil` if no objects match.
- parameter predicate: The predicate with which to filter the objects.
*/
public func index(matching predicate: NSPredicate) -> Int? {
return notFoundToNil(index: rlmResults.indexOfObject(with: predicate))
}
/**
Returns the index of the first object matching the predicate, or `nil` if no objects match.
- parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments.
*/
public func index(matching predicateFormat: String, _ args: Any...) -> Int? {
return notFoundToNil(index: rlmResults.indexOfObject(with: NSPredicate(format: predicateFormat,
argumentArray: unwrapOptionals(in: args))))
}
// MARK: Object Retrieval
/**
Returns the object at the given `index`.
- parameter index: The index.
*/
public subscript(position: Int) -> Element {
throwForNegativeIndex(position)
return dynamicBridgeCast(fromObjectiveC: rlmResults.object(at: UInt(position)))
}
/// Returns the first object in the results, or `nil` if the results are empty.
public var first: Element? { return rlmResults.firstObject().map(dynamicBridgeCast) }
/// Returns the last object in the results, or `nil` if the results are empty.
public var last: Element? { return rlmResults.lastObject().map(dynamicBridgeCast) }
// MARK: KVC
/**
Returns an `Array` containing the results of invoking `valueForKey(_:)` with `key` on each of the results.
- parameter key: The name of the property whose values are desired.
*/
public func value(forKey key: String) -> Any? {
return value(forKeyPath: key)
}
/**
Returns an `Array` containing the results of invoking `valueForKeyPath(_:)` with `keyPath` on each of the results.
- parameter keyPath: The key path to the property whose values are desired.
*/
public func value(forKeyPath keyPath: String) -> Any? {
return rlmResults.value(forKeyPath: keyPath)
}
/**
Invokes `setValue(_:forKey:)` on each of the objects represented by the results using the specified `value` and
`key`.
- warning: This method may only be called during a write transaction.
- parameter value: The object value.
- parameter key: The name of the property whose value should be set on each object.
*/
public func setValue(_ value: Any?, forKey key: String) {
return rlmResults.setValue(value, forKeyPath: key)
}
// MARK: Filtering
/**
Returns a `Results` containing all objects matching the given predicate in the collection.
- parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments.
*/
public func filter(_ predicateFormat: String, _ args: Any...) -> Results<Element> {
return Results<Element>(rlmResults.objects(with: NSPredicate(format: predicateFormat,
argumentArray: unwrapOptionals(in: args))))
}
/**
Returns a `Results` containing all objects matching the given predicate in the collection.
- parameter predicate: The predicate with which to filter the objects.
*/
public func filter(_ predicate: NSPredicate) -> Results<Element> {
return Results<Element>(rlmResults.objects(with: predicate))
}
// MARK: Sorting
/**
Returns a `Results` containing the objects represented by the results, but sorted.
Objects are sorted based on the values of the given key path. For example, to sort a collection of `Student`s from
youngest to oldest based on their `age` property, you might call
`students.sorted(byKeyPath: "age", ascending: true)`.
- warning: Collections may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision
floating point, integer, and string types.
- parameter keyPath: The key path to sort by.
- parameter ascending: The direction to sort in.
*/
public func sorted(byKeyPath keyPath: String, ascending: Bool = true) -> Results<Element> {
return sorted(by: [SortDescriptor(keyPath: keyPath, ascending: ascending)])
}
/**
Returns a `Results` containing the objects represented by the results, but sorted.
- warning: Collections may only be sorted by properties of boolean, `Date`, `NSDate`, single and double-precision
floating point, integer, and string types.
- see: `sorted(byKeyPath:ascending:)`
- parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
*/
public func sorted<S: Sequence>(by sortDescriptors: S) -> Results<Element>
where S.Iterator.Element == SortDescriptor {
return Results<Element>(rlmResults.sortedResults(using: sortDescriptors.map { $0.rlmSortDescriptorValue }))
}
/**
Returns a `Results` containing distinct objects based on the specified key paths
- parameter keyPaths: The key paths used produce distinct results
*/
public func distinct<S: Sequence>(by keyPaths: S) -> Results<Element>
where S.Iterator.Element == String {
return Results<Element>(rlmResults.distinctResults(usingKeyPaths: Array(keyPaths)))
}
// MARK: Aggregate Operations
/**
Returns the minimum (lowest) value of the given property among all the results, or `nil` if the results are empty.
- warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified.
- parameter property: The name of a property whose minimum value is desired.
*/
public func min<T: MinMaxType>(ofProperty property: String) -> T? {
return rlmResults.min(ofProperty: property).map(dynamicBridgeCast)
}
/**
Returns the maximum (highest) value of the given property among all the results, or `nil` if the results are empty.
- warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified.
- parameter property: The name of a property whose minimum value is desired.
*/
public func max<T: MinMaxType>(ofProperty property: String) -> T? {
return rlmResults.max(ofProperty: property).map(dynamicBridgeCast)
}
/**
Returns the sum of the values of a given property over all the results.
- warning: Only a property whose type conforms to the `AddableType` protocol can be specified.
- parameter property: The name of a property whose values should be summed.
*/
public func sum<T: AddableType>(ofProperty property: String) -> T {
return dynamicBridgeCast(fromObjectiveC: rlmResults.sum(ofProperty: property))
}
/**
Returns the average value of a given property over all the results, or `nil` if the results are empty.
- warning: Only the name of a property whose type conforms to the `AddableType` protocol can be specified.
- parameter property: The name of a property whose average value should be calculated.
*/
public func average<T: AddableType>(ofProperty property: String) -> T? {
return rlmResults.average(ofProperty: property).map(dynamicBridgeCast)
}
// MARK: Notifications
/**
Registers a block to be called each time the collection changes.
The block will be asynchronously called with the initial results, and then called again after each write
transaction which changes either any of the objects in the collection, or which objects are in the collection.
The `change` parameter that is passed to the block reports, in the form of indices within the collection, which of
the objects were added, removed, or modified during each write transaction. See the `RealmCollectionChange`
documentation for more information on the change information supplied and an example of how to use it to update a
`UITableView`.
At the time when the block is called, the collection will be fully evaluated and up-to-date, and as long as you do
not perform a write transaction on the same thread or explicitly call `realm.refresh()`, accessing it will never
perform blocking work.
Notifications are delivered via the standard run loop, and so can't be delivered while the run loop is blocked by
other activity. When notifications can't be delivered instantly, multiple notifications may be coalesced into a
single notification. This can include the notification with the initial collection.
For example, the following code performs a write transaction immediately after adding the notification block, so
there is no opportunity for the initial notification to be delivered first. As a result, the initial notification
will reflect the state of the Realm after the write transaction.
```swift
let dogs = realm.objects(Dog.self)
print("dogs.count: \(dogs?.count)") // => 0
let token = dogs.observe { changes in
switch changes {
case .initial(let dogs):
// Will print "dogs.count: 1"
print("dogs.count: \(dogs.count)")
break
case .update:
// Will not be hit in this example
break
case .error:
break
}
}
try! realm.write {
let dog = Dog()
dog.name = "Rex"
person.dogs.append(dog)
}
// end of run loop execution context
```
You must retain the returned token for as long as you want updates to be sent to the block. To stop receiving
updates, call `invalidate()` on the token.
- warning: This method cannot be called during a write transaction, or when the containing Realm is read-only.
- parameter block: The block to be called whenever a change occurs.
- returns: A token which must be held for as long as you want updates to be delivered.
*/
public func observe(_ block: @escaping (RealmCollectionChange<Results>) -> Void) -> NotificationToken {
return rlmResults.addNotificationBlock { _, change, error in
block(RealmCollectionChange.fromObjc(value: self, change: change, error: error))
}
}
}
extension Results: RealmCollection {
// MARK: Sequence Support
/// Returns a `RLMIterator` that yields successive elements in the results.
public func makeIterator() -> RLMIterator<Element> {
return RLMIterator(collection: rlmResults)
}
/// :nodoc:
// swiftlint:disable:next identifier_name
public func _asNSFastEnumerator() -> Any {
return rlmResults
}
// MARK: Collection Support
/// The position of the first element in a non-empty collection.
/// Identical to endIndex in an empty collection.
public var startIndex: Int { return 0 }
/// The collection's "past the end" position.
/// endIndex is not a valid argument to subscript, and is always reachable from startIndex by
/// zero or more applications of successor().
public var endIndex: Int { return count }
public func index(after i: Int) -> Int { return i + 1 }
public func index(before i: Int) -> Int { return i - 1 }
/// :nodoc:
public func _observe(_ block: @escaping (RealmCollectionChange<AnyRealmCollection<Element>>) -> Void) ->
NotificationToken {
let anyCollection = AnyRealmCollection(self)
return rlmResults.addNotificationBlock { _, change, error in
block(RealmCollectionChange.fromObjc(value: anyCollection, change: change, error: error))
}
}
}
// MARK: AssistedObjectiveCBridgeable
extension Results: AssistedObjectiveCBridgeable {
static func bridging(from objectiveCValue: Any, with metadata: Any?) -> Results {
return Results(objectiveCValue as! RLMResults)
}
var bridged: (objectiveCValue: Any, metadata: Any?) {
return (objectiveCValue: rlmResults, metadata: nil)
}
}
// MARK: - Codable
#if swift(>=4.1)
extension Results: Encodable where Element: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
for value in self {
try container.encode(value)
}
}
}
#endif