--- /dev/null
+////////////////////////////////////////////////////////////////////////////
+//
+// 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 "RLMArray_Private.hpp"
+
+#import "RLMObjectSchema.h"
+#import "RLMObjectStore.h"
+#import "RLMObject_Private.h"
+#import "RLMProperty_Private.h"
+#import "RLMQueryUtil.hpp"
+#import "RLMSchema_Private.h"
+#import "RLMSwiftSupport.h"
+#import "RLMThreadSafeReference_Private.hpp"
+#import "RLMUtil.hpp"
+
+// See -countByEnumeratingWithState:objects:count
+@interface RLMArrayHolder : NSObject {
+@public
+ std::unique_ptr<id[]> items;
+}
+@end
+@implementation RLMArrayHolder
+@end
+
+@interface RLMArray () <RLMThreadConfined_Private>
+@end
+
+@implementation RLMArray {
+@public
+ // Backing array when this instance is unmanaged
+ NSMutableArray *_backingArray;
+}
+
+#pragma mark - Initializers
+
+- (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName {
+ REALM_ASSERT([objectClassName length] > 0);
+ self = [super init];
+ if (self) {
+ _objectClassName = objectClassName;
+ _type = RLMPropertyTypeObject;
+ }
+ return self;
+}
+
+- (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional {
+ self = [super init];
+ if (self) {
+ _type = type;
+ _optional = optional;
+ }
+ return self;
+}
+
+#pragma mark - Convenience wrappers used for all RLMArray types
+
+- (void)addObjects:(id<NSFastEnumeration>)objects {
+ for (id obj in objects) {
+ [self addObject:obj];
+ }
+}
+
+- (void)addObject:(id)object {
+ [self insertObject:object atIndex:self.count];
+}
+
+- (void)removeLastObject {
+ NSUInteger count = self.count;
+ if (count) {
+ [self removeObjectAtIndex:count-1];
+ }
+}
+
+- (id)objectAtIndexedSubscript:(NSUInteger)index {
+ return [self objectAtIndex:index];
+}
+
+- (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index {
+ [self replaceObjectAtIndex:index withObject:newValue];
+}
+
+- (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
+ return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]];
+}
+
+- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... {
+ va_list args;
+ va_start(args, predicateFormat);
+ NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args];
+ va_end(args);
+ return index;
+}
+
+- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args {
+ return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat
+ arguments:args]];
+}
+
+#pragma mark - Unmanaged RLMArray implementation
+
+- (RLMRealm *)realm {
+ return nil;
+}
+
+- (id)firstObject {
+ if (self.count) {
+ return [self objectAtIndex:0];
+ }
+ return nil;
+}
+
+- (id)lastObject {
+ NSUInteger count = self.count;
+ if (count) {
+ return [self objectAtIndex:count-1];
+ }
+ return nil;
+}
+
+- (id)objectAtIndex:(NSUInteger)index {
+ validateArrayBounds(self, index);
+ return [_backingArray objectAtIndex:index];
+}
+
+- (NSUInteger)count {
+ return _backingArray.count;
+}
+
+- (BOOL)isInvalidated {
+ return NO;
+}
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+ objects:(__unused __unsafe_unretained id [])buffer
+ count:(__unused NSUInteger)len {
+ if (state->state != 0) {
+ return 0;
+ }
+
+ // We need to enumerate a copy of the backing array so that it doesn't
+ // reflect changes made during enumeration. This copy has to be autoreleased
+ // (since there's nowhere for us to store a strong reference), and uses
+ // RLMArrayHolder rather than an NSArray because NSArray doesn't guarantee
+ // that it'll use a single contiguous block of memory, and if it doesn't
+ // we'd need to forward multiple calls to this method to the same NSArray,
+ // which would require holding a reference to it somewhere.
+ __autoreleasing RLMArrayHolder *copy = [[RLMArrayHolder alloc] init];
+ copy->items = std::make_unique<id[]>(self.count);
+
+ NSUInteger i = 0;
+ for (id object in _backingArray) {
+ copy->items[i++] = object;
+ }
+
+ state->itemsPtr = (__unsafe_unretained id *)(void *)copy->items.get();
+ // needs to point to something valid, but the whole point of this is so
+ // that it can't be changed
+ state->mutationsPtr = state->extra;
+ state->state = i;
+
+ return i;
+}
+
+
+template<typename IndexSetFactory>
+static void changeArray(__unsafe_unretained RLMArray *const ar,
+ NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
+ if (!ar->_backingArray) {
+ ar->_backingArray = [NSMutableArray new];
+ }
+
+ if (RLMObjectBase *parent = ar->_parentObject) {
+ NSIndexSet *indexes = is();
+ [parent willChange:kind valuesAtIndexes:indexes forKey:ar->_key];
+ f();
+ [parent didChange:kind valuesAtIndexes:indexes forKey:ar->_key];
+ }
+ else {
+ f();
+ }
+}
+
+static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
+ NSUInteger index, dispatch_block_t f) {
+ changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
+}
+
+static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
+ NSRange range, dispatch_block_t f) {
+ changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
+}
+
+static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
+ NSIndexSet *is, dispatch_block_t f) {
+ changeArray(ar, kind, f, [=] { return is; });
+}
+
+void RLMArrayValidateMatchingObjectType(__unsafe_unretained RLMArray *const array,
+ __unsafe_unretained id const value) {
+ if (!value && !array->_optional) {
+ @throw RLMException(@"Invalid nil value for array of '%@'.",
+ array->_objectClassName ?: RLMTypeToString(array->_type));
+ }
+ if (array->_type != RLMPropertyTypeObject) {
+ if (!RLMValidateValue(value, array->_type, array->_optional, false, nil)) {
+ @throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@%s'.",
+ value, [value class], RLMTypeToString(array->_type),
+ array->_optional ? "?" : "");
+ }
+ return;
+ }
+
+ auto object = RLMDynamicCast<RLMObjectBase>(value);
+ if (!object) {
+ return;
+ }
+ if (!object->_objectSchema) {
+ @throw RLMException(@"Object cannot be inserted unless the schema is initialized. "
+ "This can happen if you try to insert objects into a RLMArray / List from a default value or from an overriden unmanaged initializer (`init()`).");
+ }
+ if (![array->_objectClassName isEqualToString:object->_objectSchema.className]) {
+ @throw RLMException(@"Object of type '%@' does not match RLMArray type '%@'.",
+ object->_objectSchema.className, array->_objectClassName);
+ }
+}
+
+static void validateArrayBounds(__unsafe_unretained RLMArray *const ar,
+ NSUInteger index, bool allowOnePastEnd=false) {
+ NSUInteger max = ar->_backingArray.count + allowOnePastEnd;
+ if (index >= max) {
+ @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).",
+ (unsigned long long)index, (unsigned long long)max);
+ }
+}
+
+- (void)addObjectsFromArray:(NSArray *)array {
+ for (id obj in array) {
+ RLMArrayValidateMatchingObjectType(self, obj);
+ }
+ changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(_backingArray.count, array.count), ^{
+ [_backingArray addObjectsFromArray:array];
+ });
+}
+
+- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
+ RLMArrayValidateMatchingObjectType(self, anObject);
+ validateArrayBounds(self, index, true);
+ changeArray(self, NSKeyValueChangeInsertion, index, ^{
+ [_backingArray insertObject:anObject atIndex:index];
+ });
+}
+
+- (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
+ changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
+ NSUInteger currentIndex = [indexes firstIndex];
+ for (RLMObject *obj in objects) {
+ RLMArrayValidateMatchingObjectType(self, obj);
+ [_backingArray insertObject:obj atIndex:currentIndex];
+ currentIndex = [indexes indexGreaterThanIndex:currentIndex];
+ }
+ });
+}
+
+- (void)removeObjectAtIndex:(NSUInteger)index {
+ validateArrayBounds(self, index);
+ changeArray(self, NSKeyValueChangeRemoval, index, ^{
+ [_backingArray removeObjectAtIndex:index];
+ });
+}
+
+- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
+ changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
+ [_backingArray removeObjectsAtIndexes:indexes];
+ });
+}
+
+- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
+ RLMArrayValidateMatchingObjectType(self, anObject);
+ validateArrayBounds(self, index);
+ changeArray(self, NSKeyValueChangeReplacement, index, ^{
+ [_backingArray replaceObjectAtIndex:index withObject:anObject];
+ });
+}
+
+- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
+ validateArrayBounds(self, sourceIndex);
+ validateArrayBounds(self, destinationIndex);
+ id original = _backingArray[sourceIndex];
+
+ auto start = std::min(sourceIndex, destinationIndex);
+ auto len = std::max(sourceIndex, destinationIndex) - start + 1;
+ changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
+ [_backingArray removeObjectAtIndex:sourceIndex];
+ [_backingArray insertObject:original atIndex:destinationIndex];
+ });
+}
+
+- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
+ validateArrayBounds(self, index1);
+ validateArrayBounds(self, index2);
+
+ changeArray(self, NSKeyValueChangeReplacement, ^{
+ [_backingArray exchangeObjectAtIndex:index1 withObjectAtIndex:index2];
+ }, [=] {
+ NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
+ [set addIndex:index2];
+ return set;
+ });
+}
+
+- (NSUInteger)indexOfObject:(id)object {
+ RLMArrayValidateMatchingObjectType(self, object);
+ if (!_backingArray) {
+ return NSNotFound;
+ }
+ if (_type != RLMPropertyTypeObject) {
+ return [_backingArray indexOfObject:object];
+ }
+
+ NSUInteger index = 0;
+ for (RLMObjectBase *cmp in _backingArray) {
+ if (RLMObjectBaseAreEqual(object, cmp)) {
+ return index;
+ }
+ index++;
+ }
+ return NSNotFound;
+}
+
+- (void)removeAllObjects {
+ changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingArray.count), ^{
+ [_backingArray removeAllObjects];
+ });
+}
+
+- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
+ va_list args;
+ va_start(args, predicateFormat);
+ RLMResults *results = [self objectsWhere:predicateFormat args:args];
+ va_end(args);
+ return results;
+}
+
+- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
+ return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
+}
+
+static bool canAggregate(RLMPropertyType type, bool allowDate) {
+ switch (type) {
+ case RLMPropertyTypeInt:
+ case RLMPropertyTypeFloat:
+ case RLMPropertyTypeDouble:
+ return true;
+ case RLMPropertyTypeDate:
+ return allowDate;
+ default:
+ return false;
+ }
+}
+
+- (RLMPropertyType)typeForProperty:(NSString *)propertyName {
+ if ([propertyName isEqualToString:@"self"]) {
+ return _type;
+ }
+
+ RLMObjectSchema *objectSchema;
+ if (_backingArray.count) {
+ objectSchema = [_backingArray[0] objectSchema];
+ }
+ else {
+ objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName];
+ }
+
+ return RLMValidatedProperty(objectSchema, propertyName).type;
+}
+
+- (id)aggregateProperty:(NSString *)key operation:(NSString *)op method:(SEL)sel {
+ // Although delegating to valueForKeyPath: here would allow to support
+ // nested key paths as well, limiting functionality gives consistency
+ // between unmanaged and managed arrays.
+ if ([key rangeOfString:@"."].location != NSNotFound) {
+ @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators.");
+ }
+
+ bool allowDate = false;
+ bool sum = false;
+ if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) {
+ allowDate = true;
+ }
+ else if ([op isEqualToString:@"@sum"]) {
+ sum = true;
+ }
+ else if (![op isEqualToString:@"@avg"]) {
+ // Just delegate to NSArray for all other operators
+ return [_backingArray valueForKeyPath:[op stringByAppendingPathExtension:key]];
+ }
+
+ RLMPropertyType type = [self typeForProperty:key];
+ if (!canAggregate(type, allowDate)) {
+ NSString *method = sel ? NSStringFromSelector(sel) : op;
+ if (_type == RLMPropertyTypeObject) {
+ @throw RLMException(@"%@: is not supported for %@ property '%@.%@'",
+ method, RLMTypeToString(type), _objectClassName, key);
+ }
+ else {
+ @throw RLMException(@"%@ is not supported for %@%s array",
+ method, RLMTypeToString(_type), _optional ? "?" : "");
+ }
+ }
+
+ NSArray *values = [key isEqualToString:@"self"] ? _backingArray : [_backingArray valueForKey:key];
+ if (_optional) {
+ // Filter out NSNull values to match our behavior on managed arrays
+ NSIndexSet *nonnull = [values indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
+ return obj != NSNull.null;
+ }];
+ if (nonnull.count < values.count) {
+ values = [values objectsAtIndexes:nonnull];
+ }
+ }
+ id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]];
+ return sum && !result ? @0 : result;
+}
+
+- (id)valueForKeyPath:(NSString *)keyPath {
+ if ([keyPath characterAtIndex:0] != '@') {
+ return _backingArray ? [_backingArray valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath];
+ }
+
+ if (!_backingArray) {
+ _backingArray = [NSMutableArray new];
+ }
+
+ NSUInteger dot = [keyPath rangeOfString:@"."].location;
+ if (dot == NSNotFound) {
+ return [_backingArray valueForKeyPath:keyPath];
+ }
+
+ NSString *op = [keyPath substringToIndex:dot];
+ NSString *key = [keyPath substringFromIndex:dot + 1];
+ return [self aggregateProperty:key operation:op method:nil];
+}
+
+- (id)valueForKey:(NSString *)key {
+ if ([key isEqualToString:RLMInvalidatedKey]) {
+ return @NO; // Unmanaged arrays are never invalidated
+ }
+ if (!_backingArray) {
+ _backingArray = [NSMutableArray new];
+ }
+ return [_backingArray valueForKey:key];
+}
+
+- (void)setValue:(id)value forKey:(NSString *)key {
+ if ([key isEqualToString:@"self"]) {
+ RLMArrayValidateMatchingObjectType(self, value);
+ for (NSUInteger i = 0, count = _backingArray.count; i < count; ++i) {
+ _backingArray[i] = value;
+ }
+ return;
+ }
+ else if (_type == RLMPropertyTypeObject) {
+ [_backingArray setValue:value forKey:key];
+ }
+ else {
+ [self setValue:value forUndefinedKey:key];
+ }
+}
+
+- (id)minOfProperty:(NSString *)property {
+ return [self aggregateProperty:property operation:@"@min" method:_cmd];
+}
+
+- (id)maxOfProperty:(NSString *)property {
+ return [self aggregateProperty:property operation:@"@max" method:_cmd];
+}
+
+- (id)sumOfProperty:(NSString *)property {
+ return [self aggregateProperty:property operation:@"@sum" method:_cmd];
+}
+
+- (id)averageOfProperty:(NSString *)property {
+ return [self aggregateProperty:property operation:@"@avg" method:_cmd];
+}
+
+- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
+ if (!_backingArray) {
+ return NSNotFound;
+ }
+ return [_backingArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
+ return [predicate evaluateWithObject:obj];
+ }];
+}
+
+- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
+ if (!_backingArray) {
+ _backingArray = [NSMutableArray new];
+ }
+ return [_backingArray objectsAtIndexes:indexes];
+}
+
+- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
+ options:(NSKeyValueObservingOptions)options context:(void *)context {
+ RLMValidateArrayObservationKey(keyPath, self);
+ [super addObserver:observer forKeyPath:keyPath options:options context:context];
+}
+
+#pragma mark - Methods unsupported on unmanaged RLMArray instances
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
+ @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
+}
+
+- (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
+ @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
+}
+
+// The compiler complains about the method's argument type not matching due to
+// it not having the generic type attached, but it doesn't seem to be possible
+// to actually include the generic type
+// http://www.openradar.me/radar?id=6135653276319744
+#pragma clang diagnostic ignored "-Wmismatched-parameter-types"
+- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
+ @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
+}
+
+#pragma mark - Thread Confined Protocol Conformance
+
+- (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
+ REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
+}
+
+- (id)objectiveCMetadata {
+ REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
+}
+
++ (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
+ metadata:(id)metadata
+ realm:(RLMRealm *)realm {
+ REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
+}
+
+#pragma clang diagnostic pop // unused parameter warning
+
+#pragma mark - Superclass Overrides
+
+- (NSString *)description {
+ return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
+}
+
+- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
+ return RLMDescriptionWithMaxDepth(@"RLMArray", self, depth);
+}
+@end
+
+@implementation RLMSortDescriptor
+
++ (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
+ RLMSortDescriptor *desc = [[RLMSortDescriptor alloc] init];
+ desc->_keyPath = keyPath;
+ desc->_ascending = ascending;
+ return desc;
+}
+
+- (instancetype)reversedSortDescriptor {
+ return [self.class sortDescriptorWithKeyPath:_keyPath ascending:!_ascending];
+}
+
+@end