added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMArray.mm
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2014 Realm Inc.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #import "RLMArray_Private.hpp"
20
21 #import "RLMObjectSchema.h"
22 #import "RLMObjectStore.h"
23 #import "RLMObject_Private.h"
24 #import "RLMProperty_Private.h"
25 #import "RLMQueryUtil.hpp"
26 #import "RLMSchema_Private.h"
27 #import "RLMSwiftSupport.h"
28 #import "RLMThreadSafeReference_Private.hpp"
29 #import "RLMUtil.hpp"
30
31 // See -countByEnumeratingWithState:objects:count
32 @interface RLMArrayHolder : NSObject {
33 @public
34     std::unique_ptr<id[]> items;
35 }
36 @end
37 @implementation RLMArrayHolder
38 @end
39
40 @interface RLMArray () <RLMThreadConfined_Private>
41 @end
42
43 @implementation RLMArray {
44 @public
45     // Backing array when this instance is unmanaged
46     NSMutableArray *_backingArray;
47 }
48
49 #pragma mark - Initializers
50
51 - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName {
52     REALM_ASSERT([objectClassName length] > 0);
53     self = [super init];
54     if (self) {
55         _objectClassName = objectClassName;
56         _type = RLMPropertyTypeObject;
57     }
58     return self;
59 }
60
61 - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional {
62     self = [super init];
63     if (self) {
64         _type = type;
65         _optional = optional;
66     }
67     return self;
68 }
69
70 #pragma mark - Convenience wrappers used for all RLMArray types
71
72 - (void)addObjects:(id<NSFastEnumeration>)objects {
73     for (id obj in objects) {
74         [self addObject:obj];
75     }
76 }
77
78 - (void)addObject:(id)object {
79     [self insertObject:object atIndex:self.count];
80 }
81
82 - (void)removeLastObject {
83     NSUInteger count = self.count;
84     if (count) {
85         [self removeObjectAtIndex:count-1];
86     }
87 }
88
89 - (id)objectAtIndexedSubscript:(NSUInteger)index {
90     return [self objectAtIndex:index];
91 }
92
93 - (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index {
94     [self replaceObjectAtIndex:index withObject:newValue];
95 }
96
97 - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
98     return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]];
99 }
100
101 - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... {
102     va_list args;
103     va_start(args, predicateFormat);
104     NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args];
105     va_end(args);
106     return index;
107 }
108
109 - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args {
110     return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat
111                                                                    arguments:args]];
112 }
113
114 #pragma mark - Unmanaged RLMArray implementation
115
116 - (RLMRealm *)realm {
117     return nil;
118 }
119
120 - (id)firstObject {
121     if (self.count) {
122         return [self objectAtIndex:0];
123     }
124     return nil;
125 }
126
127 - (id)lastObject {
128     NSUInteger count = self.count;
129     if (count) {
130         return [self objectAtIndex:count-1];
131     }
132     return nil;
133 }
134
135 - (id)objectAtIndex:(NSUInteger)index {
136     validateArrayBounds(self, index);
137     return [_backingArray objectAtIndex:index];
138 }
139
140 - (NSUInteger)count {
141     return _backingArray.count;
142 }
143
144 - (BOOL)isInvalidated {
145     return NO;
146 }
147
148 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
149                                   objects:(__unused __unsafe_unretained id [])buffer
150                                     count:(__unused NSUInteger)len {
151     if (state->state != 0) {
152         return 0;
153     }
154
155     // We need to enumerate a copy of the backing array so that it doesn't
156     // reflect changes made during enumeration. This copy has to be autoreleased
157     // (since there's nowhere for us to store a strong reference), and uses
158     // RLMArrayHolder rather than an NSArray because NSArray doesn't guarantee
159     // that it'll use a single contiguous block of memory, and if it doesn't
160     // we'd need to forward multiple calls to this method to the same NSArray,
161     // which would require holding a reference to it somewhere.
162     __autoreleasing RLMArrayHolder *copy = [[RLMArrayHolder alloc] init];
163     copy->items = std::make_unique<id[]>(self.count);
164
165     NSUInteger i = 0;
166     for (id object in _backingArray) {
167         copy->items[i++] = object;
168     }
169
170     state->itemsPtr = (__unsafe_unretained id *)(void *)copy->items.get();
171     // needs to point to something valid, but the whole point of this is so
172     // that it can't be changed
173     state->mutationsPtr = state->extra;
174     state->state = i;
175
176     return i;
177 }
178
179
180 template<typename IndexSetFactory>
181 static void changeArray(__unsafe_unretained RLMArray *const ar,
182                         NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
183     if (!ar->_backingArray) {
184         ar->_backingArray = [NSMutableArray new];
185     }
186
187     if (RLMObjectBase *parent = ar->_parentObject) {
188         NSIndexSet *indexes = is();
189         [parent willChange:kind valuesAtIndexes:indexes forKey:ar->_key];
190         f();
191         [parent didChange:kind valuesAtIndexes:indexes forKey:ar->_key];
192     }
193     else {
194         f();
195     }
196 }
197
198 static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
199                         NSUInteger index, dispatch_block_t f) {
200     changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
201 }
202
203 static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
204                         NSRange range, dispatch_block_t f) {
205     changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
206 }
207
208 static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
209                         NSIndexSet *is, dispatch_block_t f) {
210     changeArray(ar, kind, f, [=] { return is; });
211 }
212
213 void RLMArrayValidateMatchingObjectType(__unsafe_unretained RLMArray *const array,
214                                         __unsafe_unretained id const value) {
215     if (!value && !array->_optional) {
216         @throw RLMException(@"Invalid nil value for array of '%@'.",
217                             array->_objectClassName ?: RLMTypeToString(array->_type));
218     }
219     if (array->_type != RLMPropertyTypeObject) {
220         if (!RLMValidateValue(value, array->_type, array->_optional, false, nil)) {
221             @throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@%s'.",
222                                 value, [value class], RLMTypeToString(array->_type),
223                                 array->_optional ? "?" : "");
224         }
225         return;
226     }
227
228     auto object = RLMDynamicCast<RLMObjectBase>(value);
229     if (!object) {
230         return;
231     }
232     if (!object->_objectSchema) {
233         @throw RLMException(@"Object cannot be inserted unless the schema is initialized. "
234                             "This can happen if you try to insert objects into a RLMArray / List from a default value or from an overriden unmanaged initializer (`init()`).");
235     }
236     if (![array->_objectClassName isEqualToString:object->_objectSchema.className]) {
237         @throw RLMException(@"Object of type '%@' does not match RLMArray type '%@'.",
238                             object->_objectSchema.className, array->_objectClassName);
239     }
240 }
241
242 static void validateArrayBounds(__unsafe_unretained RLMArray *const ar,
243                                    NSUInteger index, bool allowOnePastEnd=false) {
244     NSUInteger max = ar->_backingArray.count + allowOnePastEnd;
245     if (index >= max) {
246         @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).",
247                             (unsigned long long)index, (unsigned long long)max);
248     }
249 }
250
251 - (void)addObjectsFromArray:(NSArray *)array {
252     for (id obj in array) {
253         RLMArrayValidateMatchingObjectType(self, obj);
254     }
255     changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(_backingArray.count, array.count), ^{
256         [_backingArray addObjectsFromArray:array];
257     });
258 }
259
260 - (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
261     RLMArrayValidateMatchingObjectType(self, anObject);
262     validateArrayBounds(self, index, true);
263     changeArray(self, NSKeyValueChangeInsertion, index, ^{
264         [_backingArray insertObject:anObject atIndex:index];
265     });
266 }
267
268 - (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
269     changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
270         NSUInteger currentIndex = [indexes firstIndex];
271         for (RLMObject *obj in objects) {
272             RLMArrayValidateMatchingObjectType(self, obj);
273             [_backingArray insertObject:obj atIndex:currentIndex];
274             currentIndex = [indexes indexGreaterThanIndex:currentIndex];
275         }
276     });
277 }
278
279 - (void)removeObjectAtIndex:(NSUInteger)index {
280     validateArrayBounds(self, index);
281     changeArray(self, NSKeyValueChangeRemoval, index, ^{
282         [_backingArray removeObjectAtIndex:index];
283     });
284 }
285
286 - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
287     changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
288         [_backingArray removeObjectsAtIndexes:indexes];
289     });
290 }
291
292 - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
293     RLMArrayValidateMatchingObjectType(self, anObject);
294     validateArrayBounds(self, index);
295     changeArray(self, NSKeyValueChangeReplacement, index, ^{
296         [_backingArray replaceObjectAtIndex:index withObject:anObject];
297     });
298 }
299
300 - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
301     validateArrayBounds(self, sourceIndex);
302     validateArrayBounds(self, destinationIndex);
303     id original = _backingArray[sourceIndex];
304
305     auto start = std::min(sourceIndex, destinationIndex);
306     auto len = std::max(sourceIndex, destinationIndex) - start + 1;
307     changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
308         [_backingArray removeObjectAtIndex:sourceIndex];
309         [_backingArray insertObject:original atIndex:destinationIndex];
310     });
311 }
312
313 - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
314     validateArrayBounds(self, index1);
315     validateArrayBounds(self, index2);
316
317     changeArray(self, NSKeyValueChangeReplacement, ^{
318         [_backingArray exchangeObjectAtIndex:index1 withObjectAtIndex:index2];
319     }, [=] {
320         NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
321         [set addIndex:index2];
322         return set;
323     });
324 }
325
326 - (NSUInteger)indexOfObject:(id)object {
327     RLMArrayValidateMatchingObjectType(self, object);
328     if (!_backingArray) {
329         return NSNotFound;
330     }
331     if (_type != RLMPropertyTypeObject) {
332         return [_backingArray indexOfObject:object];
333     }
334
335     NSUInteger index = 0;
336     for (RLMObjectBase *cmp in _backingArray) {
337         if (RLMObjectBaseAreEqual(object, cmp)) {
338             return index;
339         }
340         index++;
341     }
342     return NSNotFound;
343 }
344
345 - (void)removeAllObjects {
346     changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingArray.count), ^{
347         [_backingArray removeAllObjects];
348     });
349 }
350
351 - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
352     va_list args;
353     va_start(args, predicateFormat);
354     RLMResults *results = [self objectsWhere:predicateFormat args:args];
355     va_end(args);
356     return results;
357 }
358
359 - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
360     return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
361 }
362
363 static bool canAggregate(RLMPropertyType type, bool allowDate) {
364     switch (type) {
365         case RLMPropertyTypeInt:
366         case RLMPropertyTypeFloat:
367         case RLMPropertyTypeDouble:
368             return true;
369         case RLMPropertyTypeDate:
370             return allowDate;
371         default:
372             return false;
373     }
374 }
375
376 - (RLMPropertyType)typeForProperty:(NSString *)propertyName {
377     if ([propertyName isEqualToString:@"self"]) {
378         return _type;
379     }
380
381     RLMObjectSchema *objectSchema;
382     if (_backingArray.count) {
383         objectSchema = [_backingArray[0] objectSchema];
384     }
385     else {
386         objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName];
387     }
388
389     return RLMValidatedProperty(objectSchema, propertyName).type;
390 }
391
392 - (id)aggregateProperty:(NSString *)key operation:(NSString *)op method:(SEL)sel {
393     // Although delegating to valueForKeyPath: here would allow to support
394     // nested key paths as well, limiting functionality gives consistency
395     // between unmanaged and managed arrays.
396     if ([key rangeOfString:@"."].location != NSNotFound) {
397         @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators.");
398     }
399
400     bool allowDate = false;
401     bool sum = false;
402     if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) {
403         allowDate = true;
404     }
405     else if ([op isEqualToString:@"@sum"]) {
406         sum = true;
407     }
408     else if (![op isEqualToString:@"@avg"]) {
409         // Just delegate to NSArray for all other operators
410         return [_backingArray valueForKeyPath:[op stringByAppendingPathExtension:key]];
411     }
412
413     RLMPropertyType type = [self typeForProperty:key];
414     if (!canAggregate(type, allowDate)) {
415         NSString *method = sel ? NSStringFromSelector(sel) : op;
416         if (_type == RLMPropertyTypeObject) {
417             @throw RLMException(@"%@: is not supported for %@ property '%@.%@'",
418                                 method, RLMTypeToString(type), _objectClassName, key);
419         }
420         else {
421             @throw RLMException(@"%@ is not supported for %@%s array",
422                                 method, RLMTypeToString(_type), _optional ? "?" : "");
423         }
424     }
425
426     NSArray *values = [key isEqualToString:@"self"] ? _backingArray : [_backingArray valueForKey:key];
427     if (_optional) {
428         // Filter out NSNull values to match our behavior on managed arrays
429         NSIndexSet *nonnull = [values indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
430             return obj != NSNull.null;
431         }];
432         if (nonnull.count < values.count) {
433             values = [values objectsAtIndexes:nonnull];
434         }
435     }
436     id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]];
437     return sum && !result ? @0 : result;
438 }
439
440 - (id)valueForKeyPath:(NSString *)keyPath {
441     if ([keyPath characterAtIndex:0] != '@') {
442         return _backingArray ? [_backingArray valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath];
443     }
444
445     if (!_backingArray) {
446         _backingArray = [NSMutableArray new];
447     }
448
449     NSUInteger dot = [keyPath rangeOfString:@"."].location;
450     if (dot == NSNotFound) {
451         return [_backingArray valueForKeyPath:keyPath];
452     }
453
454     NSString *op = [keyPath substringToIndex:dot];
455     NSString *key = [keyPath substringFromIndex:dot + 1];
456     return [self aggregateProperty:key operation:op method:nil];
457 }
458
459 - (id)valueForKey:(NSString *)key {
460     if ([key isEqualToString:RLMInvalidatedKey]) {
461         return @NO; // Unmanaged arrays are never invalidated
462     }
463     if (!_backingArray) {
464         _backingArray = [NSMutableArray new];
465     }
466     return [_backingArray valueForKey:key];
467 }
468
469 - (void)setValue:(id)value forKey:(NSString *)key {
470     if ([key isEqualToString:@"self"]) {
471         RLMArrayValidateMatchingObjectType(self, value);
472         for (NSUInteger i = 0, count = _backingArray.count; i < count; ++i) {
473             _backingArray[i] = value;
474         }
475         return;
476     }
477     else if (_type == RLMPropertyTypeObject) {
478         [_backingArray setValue:value forKey:key];
479     }
480     else {
481         [self setValue:value forUndefinedKey:key];
482     }
483 }
484
485 - (id)minOfProperty:(NSString *)property {
486     return [self aggregateProperty:property operation:@"@min" method:_cmd];
487 }
488
489 - (id)maxOfProperty:(NSString *)property {
490     return [self aggregateProperty:property operation:@"@max" method:_cmd];
491 }
492
493 - (id)sumOfProperty:(NSString *)property {
494     return [self aggregateProperty:property operation:@"@sum" method:_cmd];
495 }
496
497 - (id)averageOfProperty:(NSString *)property {
498     return [self aggregateProperty:property operation:@"@avg" method:_cmd];
499 }
500
501 - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
502     if (!_backingArray) {
503         return NSNotFound;
504     }
505     return [_backingArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
506         return [predicate evaluateWithObject:obj];
507     }];
508 }
509
510 - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
511     if (!_backingArray) {
512         _backingArray = [NSMutableArray new];
513     }
514     return [_backingArray objectsAtIndexes:indexes];
515 }
516
517 - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
518             options:(NSKeyValueObservingOptions)options context:(void *)context {
519     RLMValidateArrayObservationKey(keyPath, self);
520     [super addObserver:observer forKeyPath:keyPath options:options context:context];
521 }
522
523 #pragma mark - Methods unsupported on unmanaged RLMArray instances
524
525 #pragma clang diagnostic push
526 #pragma clang diagnostic ignored "-Wunused-parameter"
527
528 - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
529     @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
530 }
531
532 - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
533     @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
534 }
535
536 // The compiler complains about the method's argument type not matching due to
537 // it not having the generic type attached, but it doesn't seem to be possible
538 // to actually include the generic type
539 // http://www.openradar.me/radar?id=6135653276319744
540 #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
541 - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
542     @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
543 }
544
545 #pragma mark - Thread Confined Protocol Conformance
546
547 - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
548     REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
549 }
550
551 - (id)objectiveCMetadata {
552     REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
553 }
554
555 + (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
556                                      metadata:(id)metadata
557                                         realm:(RLMRealm *)realm {
558     REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
559 }
560
561 #pragma clang diagnostic pop // unused parameter warning
562
563 #pragma mark - Superclass Overrides
564
565 - (NSString *)description {
566     return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
567 }
568
569 - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
570     return RLMDescriptionWithMaxDepth(@"RLMArray", self, depth);
571 }
572 @end
573
574 @implementation RLMSortDescriptor
575
576 + (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
577     RLMSortDescriptor *desc = [[RLMSortDescriptor alloc] init];
578     desc->_keyPath = keyPath;
579     desc->_ascending = ascending;
580     return desc;
581 }
582
583 - (instancetype)reversedSortDescriptor {
584     return [self.class sortDescriptorWithKeyPath:_keyPath ascending:!_ascending];
585 }
586
587 @end