forked from realm/realm-swift
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRealmCollection.swift
2246 lines (1917 loc) · 102 KB
/
RealmCollection.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
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
////////////////////////////////////////////////////////////////////////////
//
// 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
/**
An iterator for a `RealmCollection` instance.
*/
@frozen public struct RLMIterator<Element: RealmCollectionValue>: IteratorProtocol {
private var generatorBase: NSFastEnumerationIterator
init(collection: RLMCollection) {
generatorBase = NSFastEnumerationIterator(collection)
}
/// Advance to the next element and return it, or `nil` if no next element exists.
public mutating func next() -> Element? {
guard let next = generatorBase.next() else { return nil }
return staticBridgeCast(fromObjectiveC: next) as Element
}
}
/// :nodoc:
public protocol _RealmMapValue {
/// The key of this element.
associatedtype Key: _MapKey
/// The value of this element.
associatedtype Value: RealmCollectionValue
}
/**
An iterator for a `RealmKeyedCollection` instance.
*/
@frozen public struct RLMMapIterator<Element: _RealmMapValue>: IteratorProtocol {
private var generatorBase: NSFastEnumerationIterator
private var collection: RLMDictionary<AnyObject, AnyObject>
init(collection: RLMDictionary<AnyObject, AnyObject>) {
self.collection = collection
generatorBase = NSFastEnumerationIterator(collection)
}
/// Advance to the next element and return it, or `nil` if no next element exists.
public mutating func next() -> Element? {
let next = generatorBase.next()
if let next = next as? Element.Key {
let key: Element.Key = next
if let val = collection[key as AnyObject].map(Element.Value._rlmFromObjc(_:)), let val {
return SingleMapEntry(key: key, value: val) as? Element
}
}
return nil
}
}
/**
An iterator for `Map<Key, Value>` which produces `(key: Key, value: Value)` pairs for each entry in the map.
*/
@frozen public struct RLMKeyValueIterator<Key: _MapKey, Value: RealmCollectionValue>: IteratorProtocol {
private var generatorBase: NSFastEnumerationIterator
private var collection: RLMDictionary<AnyObject, AnyObject>
public typealias Element = (key: Key, value: Value)
init(collection: RLMDictionary<AnyObject, AnyObject>) {
self.collection = collection
generatorBase = NSFastEnumerationIterator(collection)
}
/// Advance to the next element and return it, or `nil` if no next element exists.
public mutating func next() -> Element? {
let next = generatorBase.next()
if let key = next as? Key,
let value = collection[key as AnyObject].map(Value._rlmFromObjc(_:)), let value {
return (key: key, value: value)
}
return nil
}
}
/**
A `RealmCollectionChange` value encapsulates information about changes to collections
that are reported by Realm notifications.
The change information is available in two formats: a simple array of row
indices in the collection for each type of change, and an array of index paths
in a requested section suitable for passing directly to `UITableView`'s batch
update methods.
The arrays of indices in the `.update` case follow `UITableView`'s batching
conventions, and can be passed as-is to a table view's batch update functions after being converted to index paths.
For example, for a simple one-section table view, you can do the following:
```swift
self.notificationToken = results.observe { changes in
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
self.tableView.reloadData()
break
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the TableView
self.tableView.beginUpdates()
self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) },
with: .automatic)
self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) },
with: .automatic)
self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) },
with: .automatic)
self.tableView.endUpdates()
break
case .error(let err):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(err)")
break
}
}
```
*/
@frozen public enum RealmCollectionChange<CollectionType> {
/**
`.initial` indicates that the initial run of the query has completed (if
applicable), and the collection can now be used without performing any
blocking work.
*/
case initial(CollectionType)
/**
`.update` indicates that a write transaction has been committed which
either changed which objects are in the collection, and/or modified one
or more of the objects in the collection.
All three of the change arrays are always sorted in ascending order.
- parameter deletions: The indices in the previous version of the collection which were removed from this one.
- parameter insertions: The indices in the new collection which were added in this version.
- parameter modifications: The indices of the objects which were modified in the previous version of this collection.
*/
case update(CollectionType, deletions: [Int], insertions: [Int], modifications: [Int])
/**
Errors can no longer occur. This case is unused and will be removed in the
next major version.
*/
case error(Error)
init(value: CollectionType?, change: RLMCollectionChange?, error: Error?) {
if let error = error {
self = .error(error)
} else if let change = change {
self = .update(value!,
deletions: forceCast(change.deletions, to: [Int].self),
insertions: forceCast(change.insertions, to: [Int].self),
modifications: forceCast(change.modifications, to: [Int].self))
} else {
self = .initial(value!)
}
}
}
private func forceCast<A, U>(_ from: A, to type: U.Type) -> U {
return from as! U
}
/// A type which can be stored in a Realm List, MutableSet, Map, or Results.
///
/// Declaring additional types as conforming to this protocol will not make them
/// actually work. Most of the logic for how to store values in Realm is not
/// implemented in Swift and there is currently no extension mechanism for
/// supporting more types.
public protocol RealmCollectionValue: Hashable, _HasPersistedType where PersistedType: RealmCollectionValue {
// Get the zero/empty/nil value for this type. Used to supply a default
// when the user does not declare one in their model.
/// :nodoc:
static func _rlmDefaultValue() -> Self
}
/// A type which can appear in a Realm collection inside an Optional.
///
/// :nodoc:
public protocol _RealmCollectionValueInsideOptional: RealmCollectionValue where PersistedType: _RealmCollectionValueInsideOptional {}
extension Int: _RealmCollectionValueInsideOptional {}
extension Int8: _RealmCollectionValueInsideOptional {}
extension Int16: _RealmCollectionValueInsideOptional {}
extension Int32: _RealmCollectionValueInsideOptional {}
extension Int64: _RealmCollectionValueInsideOptional {}
extension Float: _RealmCollectionValueInsideOptional {}
extension Double: _RealmCollectionValueInsideOptional {}
extension Bool: _RealmCollectionValueInsideOptional {}
extension String: _RealmCollectionValueInsideOptional {}
extension Date: _RealmCollectionValueInsideOptional {}
extension Data: _RealmCollectionValueInsideOptional {}
extension Decimal128: _RealmCollectionValueInsideOptional {}
extension ObjectId: _RealmCollectionValueInsideOptional {}
extension UUID: _RealmCollectionValueInsideOptional {}
extension AnyRealmValue: RealmCollectionValue {}
extension Optional: RealmCollectionValue where Wrapped: _RealmCollectionValueInsideOptional {
public static func _rlmDefaultValue() -> Self {
return .none
}
}
/// :nodoc:
public protocol RealmCollectionBase: RandomAccessCollection, LazyCollectionProtocol, CustomStringConvertible, ThreadConfined where Element: RealmCollectionValue {
// This typealias was needed with Swift 3.1. It no longer is, but remains
// just in case someone was depending on it
typealias ElementType = Element
}
// MARK: - RealmCollection protocol
/**
A homogenous collection of `Object`s which can be retrieved, filtered, sorted, and operated upon.
*/
public protocol RealmCollection: RealmCollectionBase, Equatable where Iterator == RLMIterator<Element> {
// MARK: Properties
/// The Realm which manages the collection, or `nil` for unmanaged collections.
var realm: Realm? { get }
/**
Indicates if the collection can no longer be accessed.
The collection can no longer be accessed if `invalidate()` is called on the `Realm` that manages the collection.
*/
var isInvalidated: Bool { get }
/// The number of objects in the collection.
var count: Int { get }
/// A human-readable description of the objects contained in the collection.
var description: String { get }
// MARK: Object Retrieval
/// Returns the first object in the collection, or `nil` if the collection is empty.
var first: Element? { get }
/// Returns the last object in the collection, or `nil` if the collection is empty.
var last: Element? { get }
// MARK: Index Retrieval
/**
Returns the index of an object in the collection, or `nil` if the object is not present.
- parameter object: An object.
*/
func index(of object: Element) -> Int?
/**
Returns the index of the first object matching the predicate, or `nil` if no objects match.
This is only applicable to ordered collections, and will abort if the collection is unordered.
- parameter predicate: The predicate to use to filter the objects.
*/
func index(matching predicate: NSPredicate) -> Int?
/**
Returns the index of the first object matching the predicate, or `nil` if no objects match.
This is only applicable to ordered collections, and will abort if the collection is unordered.
- parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments.
*/
func index(matching predicateFormat: String, _ args: Any...) -> Int?
// MARK: Object Retrieval
/**
Returns an array containing the objects in the collection at the indexes specified by a given index set.
- warning: Throws if an index supplied in the IndexSet is out of bounds.
- parameter indexes: The indexes in the collection to select objects from.
*/
func objects(at indexes: IndexSet) -> [Element]
// 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.
*/
func filter(_ predicateFormat: String, _ args: Any...) -> Results<Element>
/**
Returns a `Results` containing all objects matching the given predicate in the collection.
- parameter predicate: The predicate to use to filter the objects.
*/
func filter(_ predicate: NSPredicate) -> Results<Element>
// MARK: Sorting
/**
Returns a `Results` containing the objects in the collection, 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.
*/
func sorted<S: Sequence>(by sortDescriptors: S) -> Results<Element> where S.Iterator.Element == SortDescriptor
/**
Returns a `Results` containing distinct objects based on the specified key paths.
- parameter keyPaths: The key paths to distinct on.
*/
func distinct<S: Sequence>(by keyPaths: S) -> Results<Element> where S.Iterator.Element == String
// MARK: Aggregate Operations
/**
Returns the minimum (lowest) value of the given property among all the objects in the collection, or `nil` if the
collection is 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.
*/
func min<T: _HasPersistedType>(ofProperty property: String) -> T? where T.PersistedType: MinMaxType
/**
Returns the maximum (highest) value of the given property among all the objects in the collection, or `nil` if the
collection is 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.
*/
func max<T: _HasPersistedType>(ofProperty property: String) -> T? where T.PersistedType: MinMaxType
/**
Returns the sum of the given property for objects in the collection, or `nil` if the collection is empty.
- warning: Only names of properties of a type conforming to the `AddableType` protocol can be used.
- parameter property: The name of a property conforming to `AddableType` to calculate sum on.
*/
func sum<T: _HasPersistedType>(ofProperty property: String) -> T where T.PersistedType: AddableType
/**
Returns the average value of a given property over all the objects in the collection, or `nil` if
the collection is empty.
- 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.
*/
func average<T: _HasPersistedType>(ofProperty property: String) -> T? where T.PersistedType: AddableType
// MARK: Key-Value Coding
/**
Returns an `Array` containing the results of invoking `valueForKey(_:)` with `key` on each of the collection's
objects.
- parameter key: The name of the property whose values are desired.
*/
func value(forKey key: String) -> Any?
/**
Returns an `Array` containing the results of invoking `valueForKeyPath(_:)` with `keyPath` on each of the
collection's objects.
- parameter keyPath: The key path to the property whose values are desired.
*/
func value(forKeyPath keyPath: String) -> Any?
/**
Invokes `setValue(_:forKey:)` on each of the collection's objects 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.
*/
func setValue(_ value: Any?, forKey key: String)
// 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.
If no queue is given, notifications are delivered via the standard run loop, and so can't be delivered while the
run loop is blocked by other activity. If a queue is given, notifications are delivered to that queue instead. 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
```
If no key paths are given, the block will be executed on any insertion,
modification, or deletion for all object properties and the properties of
any nested, linked objects. If a key path or key paths are provided,
then the block will be called for changes which occur only on the
provided key paths. For example, if:
```swift
class Dog: Object {
@Persisted var name: String
@Persisted var age: Int
@Persisted var toys: List<Toy>
}
// ...
let dogs = realm.objects(Dog.self)
let token = dogs.observe(keyPaths: ["name"]) { changes in
switch changes {
case .initial(let dogs):
// ...
case .update:
// This case is hit:
// - after the token is initialized
// - when the name property of an object in the
// collection is modified
// - when an element is inserted or removed
// from the collection.
// This block is not triggered:
// - when a value other than name is modified on
// one of the elements.
case .error:
// ...
}
}
// end of run loop execution context
```
- If the observed key path were `["toys.brand"]`, then any insertion or
deletion to the `toys` list on any of the collection's elements would trigger the block.
Changes to the `brand` value on any `Toy` that is linked to a `Dog` in this
collection will trigger the block. Changes to a value other than `brand` on any `Toy` that
is linked to a `Dog` in this collection would not trigger the block.
Any insertion or removal to the `Dog` type collection being observed
would also trigger a notification.
- If the above example observed the `["toys"]` key path, then any insertion,
deletion, or modification to the `toys` list for any element in the collection
would trigger the block.
Changes to any value on any `Toy` that is linked to a `Dog` in this collection
would *not* trigger the block.
Any insertion or removal to the `Dog` type collection being observed
would still trigger a notification.
- note: Multiple notification tokens on the same object which filter for
separate key paths *do not* filter exclusively. If one key path
change is satisfied for one notification token, then all notification
token blocks for that object will execute.
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 keyPaths: Only properties contained in the key paths array will trigger
the block when they are modified. If `nil`, notifications
will be delivered for any property change on the object.
String key paths which do not correspond to a valid a property
will throw an exception. See description above for
more detail on linked properties.
- parameter queue: The serial dispatch queue to receive notification on. If
`nil`, notifications are delivered to the current thread.
- 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.
*/
func observe(keyPaths: [String]?,
on queue: DispatchQueue?,
_ block: @escaping (RealmCollectionChange<Self>) -> Void) -> NotificationToken
#if compiler(<6)
/**
Registers a block to be called each time the collection changes.
The block will be asynchronously called with an initial version of the
collection, 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 `actor` parameter passed to the block is the actor which you pass to this
function. This parameter is required to isolate the callback to the actor.
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 after the previous notification. The `collection` field in the change
enum will be isolated to the requested actor, and is safe to use within that
actor only. See the ``RealmCollectionChange`` documentation for more
information on the change information supplied and an example of how to use it
to update a `UITableView`.
Once the initial notification is delivered, the collection will be fully
evaluated and up-to-date, and accessing it will never perform any blocking
work. This guarantee holds only as long as you do not perform a write
transaction on the same actor as notifications are being delivered to. If you
do, accessing the collection before the next notification is delivered may need
to rerun the query.
Notifications are delivered to the given actor's executor. 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: any writes which occur before the initial notification is delivered
may not produce change notifications.
Adding, removing or assigning objects in the collection always produces a
notification. By default, modifying the objects which a collection links to
(and the objects which those objects link to, if applicable) will also report
that index in the collection as being modified. If a non-empty array of
keypaths is provided, then only modifications to those keypaths will mark the
object as modified. For example:
```swift
class Dog: Object {
@Persisted var name: String
@Persisted var age: Int
@Persisted var toys: List<Toy>
}
let dogs = realm.objects(Dog.self)
let token = await dogs.observe(keyPaths: ["name"], on: myActor) { actor, changes in
switch changes {
case .initial(let dogs):
// Query has finished running and dogs can not be used without blocking
case .update:
// This case is hit:
// - after the token is initialized
// - when the name property of an object in the collection is modified
// - when an element is inserted or removed from the collection.
// This block is not triggered:
// - when a value other than name is modified on one of the elements.
case .error:
// Can no longer happen but is left for backwards compatiblity
}
}
```
- If the observed key path were `["toys.brand"]`, then any insertion or
deletion to the `toys` list on any of the collection's elements would trigger
the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog`
in this collection will trigger the block. Changes to a value other than
`brand` on any `Toy` that is linked to a `Dog` in this collection would not
trigger the block. Any insertion or removal to the `Dog` type collection being
observed would also trigger a notification.
- If the above example observed the `["toys"]` key path, then any insertion,
deletion, or modification to the `toys` list for any element in the collection
would trigger the block. Changes to any value on any `Toy` that is linked to a
`Dog` in this collection would *not* trigger the block. Any insertion or
removal to the `Dog` type collection being observed would still trigger a
notification.
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 keyPaths: Only properties contained in the key paths array will trigger
the block when they are modified. If `nil` or empty, notifications
will be delivered for any property change on the object.
String key paths which do not correspond to a valid a property
will throw an exception. See description above for
more detail on linked properties.
- parameter actor: The actor to isolate the notifications to.
- 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.
*/
@available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *)
@_unsafeInheritExecutor
func observe<A: Actor>(keyPaths: [String]?,
on actor: A,
_ block: @Sendable @escaping (isolated A, RealmCollectionChange<Self>) -> Void) async -> NotificationToken
#else
/**
Registers a block to be called each time the collection changes.
The block will be asynchronously called with an initial version of the
collection, 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 `actor` parameter passed to the block is the actor which you pass to this
function. This parameter is required to isolate the callback to the actor.
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 after the previous notification. The `collection` field in the change
enum will be isolated to the requested actor, and is safe to use within that
actor only. See the ``RealmCollectionChange`` documentation for more
information on the change information supplied and an example of how to use it
to update a `UITableView`.
Once the initial notification is delivered, the collection will be fully
evaluated and up-to-date, and accessing it will never perform any blocking
work. This guarantee holds only as long as you do not perform a write
transaction on the same actor as notifications are being delivered to. If you
do, accessing the collection before the next notification is delivered may need
to rerun the query.
Notifications are delivered to the given actor's executor. 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: any writes which occur before the initial notification is delivered
may not produce change notifications.
Adding, removing or assigning objects in the collection always produces a
notification. By default, modifying the objects which a collection links to
(and the objects which those objects link to, if applicable) will also report
that index in the collection as being modified. If a non-empty array of
keypaths is provided, then only modifications to those keypaths will mark the
object as modified. For example:
```swift
class Dog: Object {
@Persisted var name: String
@Persisted var age: Int
@Persisted var toys: List<Toy>
}
let dogs = realm.objects(Dog.self)
let token = await dogs.observe(keyPaths: ["name"], on: myActor) { actor, changes in
switch changes {
case .initial(let dogs):
// Query has finished running and dogs can not be used without blocking
case .update:
// This case is hit:
// - after the token is initialized
// - when the name property of an object in the collection is modified
// - when an element is inserted or removed from the collection.
// This block is not triggered:
// - when a value other than name is modified on one of the elements.
case .error:
// Can no longer happen but is left for backwards compatiblity
}
}
```
- If the observed key path were `["toys.brand"]`, then any insertion or
deletion to the `toys` list on any of the collection's elements would trigger
the block. Changes to the `brand` value on any `Toy` that is linked to a `Dog`
in this collection will trigger the block. Changes to a value other than
`brand` on any `Toy` that is linked to a `Dog` in this collection would not
trigger the block. Any insertion or removal to the `Dog` type collection being
observed would also trigger a notification.
- If the above example observed the `["toys"]` key path, then any insertion,
deletion, or modification to the `toys` list for any element in the collection
would trigger the block. Changes to any value on any `Toy` that is linked to a
`Dog` in this collection would *not* trigger the block. Any insertion or
removal to the `Dog` type collection being observed would still trigger a
notification.
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 keyPaths: Only properties contained in the key paths array will trigger
the block when they are modified. If `nil` or empty, notifications
will be delivered for any property change on the object.
String key paths which do not correspond to a valid a property
will throw an exception. See description above for
more detail on linked properties.
- parameter actor: The actor to isolate the notifications to.
- 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.
*/
@available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *)
func observe<A: Actor>(keyPaths: [String]?,
on actor: A,
_isolation: isolated (any Actor)?,
_ block: @Sendable @escaping (isolated A, RealmCollectionChange<Self>) -> Void) async -> NotificationToken
#endif
// MARK: Frozen Objects
/// Returns true if this collection is frozen
var isFrozen: Bool { get }
/**
Returns a frozen (immutable) snapshot of this collection.
The frozen copy is an immutable collection which contains the same data as this collection
currently contains, but will not update when writes are made to the containing Realm. Unlike
live collections, frozen collections can be accessed from any thread.
- warning: This method cannot be called during a write transaction, or when the containing
Realm is read-only.
- warning: Holding onto a frozen collection for an extended period while performing write
transaction on the Realm may result in the Realm file growing to large sizes. See
`Realm.Configuration.maximumNumberOfActiveVersions` for more information.
*/
func freeze() -> Self
/**
Returns a live (mutable) version of this frozen collection.
This method resolves a reference to a live copy of the same frozen collection.
If called on a live collection, will return itself.
*/
func thaw() -> Self?
/**
Sorts this collection from a given array of sort descriptors and performs sectioning via a
user defined callback, returning the result as an instance of `SectionedResults`.
- parameter sortDescriptors: An array of `SortDescriptor`s to sort by.
- parameter keyBlock: A callback which is invoked on each element in the Results collection.
This callback is to return the section key for the element in the collection.
- note: The primary sort descriptor must be responsible for determining the section key.
- returns: An instance of `SectionedResults`.
*/
func sectioned<Key: _Persistable>(sortDescriptors: [SortDescriptor],
_ keyBlock: @escaping ((Element) -> Key)) -> SectionedResults<Key, Element>
}
// MARK: - Codable
extension RealmCollection where Element: Encodable {
/// Encodes the contents of this collection into the given encoder.
/// - parameter encoder The encoder to write data to.
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
for value in self {
try container.encode(value)
}
}
}
// MARK: - Type-safe queries
public extension RealmCollection {
/**
Returns the index of the first object matching the query, or `nil` if no objects match.
This is only applicable to ordered collections, and will abort if the collection is unordered.
- Note: This should only be used with classes using the `@Persistable` property declaration.
- Usage:
```
obj.index(matching: { $0.fooCol < 456 })
```
- Note: See ``Query`` for more information on what query operations are available.
- parameter isIncluded: The query closure to use to filter the objects.
*/
func index(matching isIncluded: ((Query<Element>) -> Query<Bool>)) -> Int? where Element: _RealmSchemaDiscoverable {
let isPrimitive = Element._rlmType != .object
return index(matching: isIncluded(Query<Element>(isPrimitive: isPrimitive)).predicate)
}
/**
Returns a `Results` containing all objects matching the given query in the collection.
- Note: This should only be used with classes using the `@Persistable` property declaration.
- Usage:
```
myCol.where {
($0.fooCol > 5) && ($0.barCol == "foobar")
}
```
- Note: See ``Query`` for more information on what query operations are available.
- parameter isIncluded: The query closure to use to filter the objects.
*/
func `where`(_ isIncluded: ((Query<Element>) -> Query<Bool>)) -> Results<Element> {
return filter(isIncluded(Query()).predicate)
}
}
// MARK: Collection protocol
public extension RealmCollection {
/// The position of the first element in a non-empty collection.
/// Identical to endIndex in an empty collection.
var startIndex: Int { 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().
var endIndex: Int { count }
/// Returns the position immediately after the given index.
/// - parameter i: A valid index of the collection. `i` must be less than `endIndex`.
func index(after i: Int) -> Int { return i + 1 }
/// Returns the position immediately before the given index.
/// - parameter i: A valid index of the collection. `i` must be greater than `startIndex`.
func index(before i: Int) -> Int { return i - 1 }
}
// MARK: - Aggregation
/**
Extension for RealmCollections where the Value is of an Object type that
enables aggregatable operations.
*/
public extension RealmCollection where Element: ObjectBase {
/**
Returns the minimum (lowest) value of the given property among all the objects in the collection, or `nil` if the
collection is empty.
- warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified.
- parameter keyPath: The keyPath of a property whose minimum value is desired.
*/
func min<T: _HasPersistedType>(of keyPath: KeyPath<Element, T>) -> T? where T.PersistedType: MinMaxType {
min(ofProperty: _name(for: keyPath))
}
/**
Returns the maximum (highest) value of the given property among all the objects in the collection, or `nil` if the
collection is empty.
- warning: Only a property whose type conforms to the `MinMaxType` protocol can be specified.
- parameter keyPath: The keyPath of a property whose minimum value is desired.
*/
func max<T: _HasPersistedType>(of keyPath: KeyPath<Element, T>) -> T? where T.PersistedType: MinMaxType {
max(ofProperty: _name(for: keyPath))
}
/**
Returns the sum of the given property for objects in the collection, or `nil` if the collection is empty.
- warning: Only names of properties of a type conforming to the `AddableType` protocol can be used.
- parameter keyPath: The keyPath of a property conforming to `AddableType` to calculate sum on.
*/
func sum<T: _HasPersistedType>(of keyPath: KeyPath<Element, T>) -> T where T.PersistedType: AddableType {
sum(ofProperty: _name(for: keyPath))
}
/**
Returns the average value of a given property over all the objects in the collection, or `nil` if
the collection is empty.
- warning: Only a property whose type conforms to the `AddableType` protocol can be specified.
- parameter keyPath: The keyPath of a property whose values should be summed.
*/
func average<T: _HasPersistedType>(of keyPath: KeyPath<Element, T>) -> T? where T.PersistedType: AddableType {
average(ofProperty: _name(for: keyPath))
}
/**
Sorts and sections this collection from a given property key path, returning the result
as an instance of `SectionedResults`. For every unique value retrieved from the
keyPath a section key will be generated.
- parameter keyPath: The property key path to sort & section on.
- parameter ascending: The direction to sort in.
- returns: An instance of `SectionedResults`.
*/
func sectioned<Key: _Persistable>(by keyPath: KeyPath<Element, Key>,
ascending: Bool = true) -> SectionedResults<Key, Element> where Element: ObjectBase {
return sectioned(sortDescriptors: [.init(keyPath: _name(for: keyPath), ascending: ascending)], {
return $0[keyPath: keyPath]
})
}
/**
Sorts and sections this collection from a given property key path, returning the result
as an instance of `SectionedResults`. For every unique value retrieved from the
keyPath a section key will be generated.
- parameter keyPath: The property key path to sort & section on.
- parameter sortDescriptors: An array of `SortDescriptor`s to sort by.
- note: The primary sort descriptor must be responsible for determining the section key.
- returns: An instance of `SectionedResults`.
*/
func sectioned<Key: _Persistable>(by keyPath: KeyPath<Element, Key>,
sortDescriptors: [SortDescriptor]) -> SectionedResults<Key, Element> where Element: ObjectBase {
guard let sortDescriptor = sortDescriptors.first else {
throwRealmException("Can not section Results with empty sortDescriptor parameter.")
}
let keyPathString = _name(for: keyPath)
if keyPathString != sortDescriptor.keyPath {
throwRealmException("The section key path must match the primary sort descriptor.")
}
return sectioned(sortDescriptors: sortDescriptors, { $0[keyPath: keyPath] })
}
/**
Sorts this collection from a given array of `SortDescriptor`'s and performs sectioning
via a user defined callback function.
- parameter block: A callback which is invoked on each element in the collection.
This callback is to return the section key for the element in the collection.
- parameter sortDescriptors: An array of `SortDescriptor`s to sort by.
- note: The primary sort descriptor must be responsible for determining the section key.
- returns: An instance of `SectionedResults`.
*/
func sectioned<Key: _Persistable>(by block: @escaping ((Element) -> Key),
sortDescriptors: [SortDescriptor]) -> SectionedResults<Key, Element> where Element: ObjectBase {
return sectioned(sortDescriptors: sortDescriptors, block)
}
}
public extension RealmCollection where Element.PersistedType: MinMaxType {
/**
Returns the minimum (lowest) value of the collection, or `nil` if the collection is empty.
*/
func min() -> Element? {
return min(ofProperty: "self")
}
/**
Returns the maximum (highest) value of the collection, or `nil` if the collection is empty.
*/
func max() -> Element? {
return max(ofProperty: "self")
}
}
public extension RealmCollection where Element.PersistedType: AddableType {
/**
Returns the sum of the values in the collection, or `nil` if the collection is empty.
*/
func sum() -> Element {
return sum(ofProperty: "self")
}
/**
Returns the average of all of the values in the collection.
*/
func average<T: _HasPersistedType>() -> T? where T.PersistedType: AddableType {
return average(ofProperty: "self")
}
}
// MARK: Sort and distinct
/**
Extension for RealmCollections where the Value is of an Object type that
enables sortable operations.
*/
public extension RealmCollection where Element: KeypathSortable {
/**
Returns a `Results` containing the objects in the collection, but sorted.
Objects are sorted based on the values of the given key path. For example, to sort a collection of `Student`s from