1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2014 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
19 #import "RLMArray_Private.hpp"
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"
31 // See -countByEnumeratingWithState:objects:count
32 @interface RLMArrayHolder : NSObject {
34 std::unique_ptr<id[]> items;
37 @implementation RLMArrayHolder
40 @interface RLMArray () <RLMThreadConfined_Private>
43 @implementation RLMArray {
45 // Backing array when this instance is unmanaged
46 NSMutableArray *_backingArray;
49 #pragma mark - Initializers
51 - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName {
52 REALM_ASSERT([objectClassName length] > 0);
55 _objectClassName = objectClassName;
56 _type = RLMPropertyTypeObject;
61 - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional {
70 #pragma mark - Convenience wrappers used for all RLMArray types
72 - (void)addObjects:(id<NSFastEnumeration>)objects {
73 for (id obj in objects) {
78 - (void)addObject:(id)object {
79 [self insertObject:object atIndex:self.count];
82 - (void)removeLastObject {
83 NSUInteger count = self.count;
85 [self removeObjectAtIndex:count-1];
89 - (id)objectAtIndexedSubscript:(NSUInteger)index {
90 return [self objectAtIndex:index];
93 - (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index {
94 [self replaceObjectAtIndex:index withObject:newValue];
97 - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
98 return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]];
101 - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... {
103 va_start(args, predicateFormat);
104 NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args];
109 - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args {
110 return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat
114 #pragma mark - Unmanaged RLMArray implementation
116 - (RLMRealm *)realm {
122 return [self objectAtIndex:0];
128 NSUInteger count = self.count;
130 return [self objectAtIndex:count-1];
135 - (id)objectAtIndex:(NSUInteger)index {
136 validateArrayBounds(self, index);
137 return [_backingArray objectAtIndex:index];
140 - (NSUInteger)count {
141 return _backingArray.count;
144 - (BOOL)isInvalidated {
148 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
149 objects:(__unused __unsafe_unretained id [])buffer
150 count:(__unused NSUInteger)len {
151 if (state->state != 0) {
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);
166 for (id object in _backingArray) {
167 copy->items[i++] = object;
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;
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];
187 if (RLMObjectBase *parent = ar->_parentObject) {
188 NSIndexSet *indexes = is();
189 [parent willChange:kind valuesAtIndexes:indexes forKey:ar->_key];
191 [parent didChange:kind valuesAtIndexes:indexes forKey:ar->_key];
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]; });
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]; });
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; });
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));
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 ? "?" : "");
228 auto object = RLMDynamicCast<RLMObjectBase>(value);
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()`).");
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);
242 static void validateArrayBounds(__unsafe_unretained RLMArray *const ar,
243 NSUInteger index, bool allowOnePastEnd=false) {
244 NSUInteger max = ar->_backingArray.count + allowOnePastEnd;
246 @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).",
247 (unsigned long long)index, (unsigned long long)max);
251 - (void)addObjectsFromArray:(NSArray *)array {
252 for (id obj in array) {
253 RLMArrayValidateMatchingObjectType(self, obj);
255 changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(_backingArray.count, array.count), ^{
256 [_backingArray addObjectsFromArray:array];
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];
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];
279 - (void)removeObjectAtIndex:(NSUInteger)index {
280 validateArrayBounds(self, index);
281 changeArray(self, NSKeyValueChangeRemoval, index, ^{
282 [_backingArray removeObjectAtIndex:index];
286 - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
287 changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
288 [_backingArray removeObjectsAtIndexes:indexes];
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];
300 - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
301 validateArrayBounds(self, sourceIndex);
302 validateArrayBounds(self, destinationIndex);
303 id original = _backingArray[sourceIndex];
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];
313 - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
314 validateArrayBounds(self, index1);
315 validateArrayBounds(self, index2);
317 changeArray(self, NSKeyValueChangeReplacement, ^{
318 [_backingArray exchangeObjectAtIndex:index1 withObjectAtIndex:index2];
320 NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
321 [set addIndex:index2];
326 - (NSUInteger)indexOfObject:(id)object {
327 RLMArrayValidateMatchingObjectType(self, object);
328 if (!_backingArray) {
331 if (_type != RLMPropertyTypeObject) {
332 return [_backingArray indexOfObject:object];
335 NSUInteger index = 0;
336 for (RLMObjectBase *cmp in _backingArray) {
337 if (RLMObjectBaseAreEqual(object, cmp)) {
345 - (void)removeAllObjects {
346 changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingArray.count), ^{
347 [_backingArray removeAllObjects];
351 - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
353 va_start(args, predicateFormat);
354 RLMResults *results = [self objectsWhere:predicateFormat args:args];
359 - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
360 return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
363 static bool canAggregate(RLMPropertyType type, bool allowDate) {
365 case RLMPropertyTypeInt:
366 case RLMPropertyTypeFloat:
367 case RLMPropertyTypeDouble:
369 case RLMPropertyTypeDate:
376 - (RLMPropertyType)typeForProperty:(NSString *)propertyName {
377 if ([propertyName isEqualToString:@"self"]) {
381 RLMObjectSchema *objectSchema;
382 if (_backingArray.count) {
383 objectSchema = [_backingArray[0] objectSchema];
386 objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName];
389 return RLMValidatedProperty(objectSchema, propertyName).type;
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.");
400 bool allowDate = false;
402 if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) {
405 else if ([op isEqualToString:@"@sum"]) {
408 else if (![op isEqualToString:@"@avg"]) {
409 // Just delegate to NSArray for all other operators
410 return [_backingArray valueForKeyPath:[op stringByAppendingPathExtension:key]];
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);
421 @throw RLMException(@"%@ is not supported for %@%s array",
422 method, RLMTypeToString(_type), _optional ? "?" : "");
426 NSArray *values = [key isEqualToString:@"self"] ? _backingArray : [_backingArray valueForKey:key];
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;
432 if (nonnull.count < values.count) {
433 values = [values objectsAtIndexes:nonnull];
436 id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]];
437 return sum && !result ? @0 : result;
440 - (id)valueForKeyPath:(NSString *)keyPath {
441 if ([keyPath characterAtIndex:0] != '@') {
442 return _backingArray ? [_backingArray valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath];
445 if (!_backingArray) {
446 _backingArray = [NSMutableArray new];
449 NSUInteger dot = [keyPath rangeOfString:@"."].location;
450 if (dot == NSNotFound) {
451 return [_backingArray valueForKeyPath:keyPath];
454 NSString *op = [keyPath substringToIndex:dot];
455 NSString *key = [keyPath substringFromIndex:dot + 1];
456 return [self aggregateProperty:key operation:op method:nil];
459 - (id)valueForKey:(NSString *)key {
460 if ([key isEqualToString:RLMInvalidatedKey]) {
461 return @NO; // Unmanaged arrays are never invalidated
463 if (!_backingArray) {
464 _backingArray = [NSMutableArray new];
466 return [_backingArray valueForKey:key];
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;
477 else if (_type == RLMPropertyTypeObject) {
478 [_backingArray setValue:value forKey:key];
481 [self setValue:value forUndefinedKey:key];
485 - (id)minOfProperty:(NSString *)property {
486 return [self aggregateProperty:property operation:@"@min" method:_cmd];
489 - (id)maxOfProperty:(NSString *)property {
490 return [self aggregateProperty:property operation:@"@max" method:_cmd];
493 - (id)sumOfProperty:(NSString *)property {
494 return [self aggregateProperty:property operation:@"@sum" method:_cmd];
497 - (id)averageOfProperty:(NSString *)property {
498 return [self aggregateProperty:property operation:@"@avg" method:_cmd];
501 - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
502 if (!_backingArray) {
505 return [_backingArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
506 return [predicate evaluateWithObject:obj];
510 - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
511 if (!_backingArray) {
512 _backingArray = [NSMutableArray new];
514 return [_backingArray objectsAtIndexes:indexes];
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];
523 #pragma mark - Methods unsupported on unmanaged RLMArray instances
525 #pragma clang diagnostic push
526 #pragma clang diagnostic ignored "-Wunused-parameter"
528 - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
529 @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
532 - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
533 @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
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");
545 #pragma mark - Thread Confined Protocol Conformance
547 - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
548 REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
551 - (id)objectiveCMetadata {
552 REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
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`");
561 #pragma clang diagnostic pop // unused parameter warning
563 #pragma mark - Superclass Overrides
565 - (NSString *)description {
566 return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
569 - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
570 return RLMDescriptionWithMaxDepth(@"RLMArray", self, depth);
574 @implementation RLMSortDescriptor
576 + (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
577 RLMSortDescriptor *desc = [[RLMSortDescriptor alloc] init];
578 desc->_keyPath = keyPath;
579 desc->_ascending = ascending;
583 - (instancetype)reversedSortDescriptor {
584 return [self.class sortDescriptorWithKeyPath:_keyPath ascending:!_ascending];