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 "RLMAccessor.hpp"
22 #import "RLMObjectSchema_Private.hpp"
23 #import "RLMObjectStore.h"
24 #import "RLMObject_Private.hpp"
25 #import "RLMObservation.hpp"
26 #import "RLMProperty_Private.h"
27 #import "RLMQueryUtil.hpp"
28 #import "RLMRealm_Private.hpp"
30 #import "RLMThreadSafeReference_Private.hpp"
35 #import "shared_realm.hpp"
37 #import <realm/table_view.hpp>
38 #import <objc/runtime.h>
40 @interface RLMManagedArrayHandoverMetadata : NSObject
41 @property (nonatomic) NSString *parentClassName;
42 @property (nonatomic) NSString *key;
45 @implementation RLMManagedArrayHandoverMetadata
48 @interface RLMManagedArray () <RLMThreadConfined_Private>
52 // RLMArray implementation
54 @implementation RLMManagedArray {
56 realm::List _backingList;
58 RLMClassInfo *_objectInfo;
59 RLMClassInfo *_ownerInfo;
60 std::unique_ptr<RLMObservationInfo> _observationInfo;
63 - (RLMManagedArray *)initWithList:(realm::List)list
64 realm:(__unsafe_unretained RLMRealm *const)realm
65 parentInfo:(RLMClassInfo *)parentInfo
66 property:(__unsafe_unretained RLMProperty *const)property {
67 if (property.type == RLMPropertyTypeObject)
68 self = [self initWithObjectClassName:property.objectClassName];
70 self = [self initWithObjectType:property.type optional:property.optional];
73 REALM_ASSERT(list.get_realm() == realm->_realm);
74 _backingList = std::move(list);
75 _ownerInfo = parentInfo;
76 if (property.type == RLMPropertyTypeObject)
77 _objectInfo = &parentInfo->linkTargetType(property.index);
79 _objectInfo = _ownerInfo;
85 - (RLMManagedArray *)initWithParent:(__unsafe_unretained RLMObjectBase *const)parentObject
86 property:(__unsafe_unretained RLMProperty *const)property {
87 __unsafe_unretained RLMRealm *const realm = parentObject->_realm;
88 auto col = parentObject->_info->tableColumn(property);
89 auto& row = parentObject->_row;
90 return [self initWithList:realm::List(realm->_realm, *row.get_table(), col, row.get_index())
92 parentInfo:parentObject->_info
96 void RLMValidateArrayObservationKey(__unsafe_unretained NSString *const keyPath,
97 __unsafe_unretained RLMArray *const array) {
98 if (![keyPath isEqualToString:RLMInvalidatedKey]) {
99 @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@",
100 [array class], array, keyPath);
104 void RLMEnsureArrayObservationInfo(std::unique_ptr<RLMObservationInfo>& info,
105 __unsafe_unretained NSString *const keyPath,
106 __unsafe_unretained RLMArray *const array,
107 __unsafe_unretained id const observed) {
108 RLMValidateArrayObservationKey(keyPath, array);
109 if (!info && array.class == [RLMManagedArray class]) {
110 auto lv = static_cast<RLMManagedArray *>(array);
111 info = std::make_unique<RLMObservationInfo>(*lv->_ownerInfo,
112 lv->_backingList.get_origin_row_index(),
118 // validation helpers
122 static void throwError(__unsafe_unretained RLMManagedArray *const ar, NSString *aggregateMethod) {
126 catch (realm::InvalidTransactionException const&) {
127 @throw RLMException(@"Cannot modify managed RLMArray outside of a write transaction.");
129 catch (realm::IncorrectThreadException const&) {
130 @throw RLMException(@"Realm accessed from incorrect thread.");
132 catch (realm::List::InvalidatedException const&) {
133 @throw RLMException(@"RLMArray has been invalidated or the containing object has been deleted.");
135 catch (realm::List::OutOfBoundsIndexException const& e) {
136 @throw RLMException(@"Index %zu is out of bounds (must be less than %zu).",
137 e.requested, e.valid_count);
139 catch (realm::Results::UnsupportedColumnTypeException const& e) {
140 if (ar->_backingList.get_type() == realm::PropertyType::Object) {
141 @throw RLMException(@"%@: is not supported for %s%s property '%s'.",
143 string_for_property_type(e.property_type),
144 is_nullable(e.property_type) ? "?" : "",
145 e.column_name.data());
147 @throw RLMException(@"%@: is not supported for %s%s array '%@.%@'.",
149 string_for_property_type(e.property_type),
150 is_nullable(e.property_type) ? "?" : "",
151 ar->_ownerInfo->rlmObjectSchema.className, ar->_key);
153 catch (std::logic_error const& e) {
154 @throw RLMException(e);
158 template<typename Function>
159 static auto translateErrors(__unsafe_unretained RLMManagedArray *const ar,
160 Function&& f, NSString *aggregateMethod=nil) {
165 throwError(ar, aggregateMethod);
169 template<typename Function>
170 static auto translateErrors(Function&& f) {
175 throwError(nil, nil);
179 template<typename IndexSetFactory>
180 static void changeArray(__unsafe_unretained RLMManagedArray *const ar,
181 NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
182 translateErrors([&] { ar->_backingList.verify_in_transaction(); });
183 RLMObservationInfo *info = RLMGetObservationInfo(ar->_observationInfo.get(),
184 ar->_backingList.get_origin_row_index(),
187 NSIndexSet *indexes = is();
188 info->willChange(ar->_key, kind, indexes);
193 info->didChange(ar->_key, kind, indexes);
196 info->didChange(ar->_key, kind, indexes);
199 translateErrors([&] { f(); });
203 static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) {
204 changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
207 static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) {
208 changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
211 static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) {
212 changeArray(ar, kind, f, [=] { return is; });
216 // public method implementations
218 - (RLMRealm *)realm {
222 - (NSUInteger)count {
223 return translateErrors([&] { return _backingList.size(); });
226 - (BOOL)isInvalidated {
227 return translateErrors([&] { return !_backingList.is_valid(); });
230 - (RLMClassInfo *)objectInfo {
235 - (bool)isBackedByList:(realm::List const&)list {
236 return _backingList == list;
239 - (BOOL)isEqual:(id)object {
240 return [object respondsToSelector:@selector(isBackedByList:)] && [object isBackedByList:_backingList];
244 return std::hash<realm::List>()(_backingList);
247 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
248 objects:(__unused __unsafe_unretained id [])buffer
249 count:(NSUInteger)len {
250 return RLMFastEnumerate(state, len, self);
253 - (id)objectAtIndex:(NSUInteger)index {
254 return translateErrors([&] {
255 RLMAccessorContext context(_realm, *_objectInfo);
256 return _backingList.get(context, index);
260 static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
261 RLMArrayValidateMatchingObjectType(ar, object);
262 if (index == NSUIntegerMax) {
263 index = translateErrors([&] { return ar->_backingList.size(); });
266 changeArray(ar, NSKeyValueChangeInsertion, index, ^{
267 RLMAccessorContext context(ar->_realm, *ar->_objectInfo);
268 ar->_backingList.insert(context, index, object);
272 - (void)addObject:(id)object {
273 RLMInsertObject(self, object, NSUIntegerMax);
276 - (void)insertObject:(id)object atIndex:(NSUInteger)index {
277 RLMInsertObject(self, object, index);
280 - (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
281 changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
282 NSUInteger index = [indexes firstIndex];
283 RLMAccessorContext context(_realm, *_objectInfo);
284 for (id obj in objects) {
285 RLMArrayValidateMatchingObjectType(self, obj);
286 _backingList.insert(context, index, obj);
287 index = [indexes indexGreaterThanIndex:index];
293 - (void)removeObjectAtIndex:(NSUInteger)index {
294 changeArray(self, NSKeyValueChangeRemoval, index, ^{
295 _backingList.remove(index);
299 - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
300 changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
301 [indexes enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *) {
302 _backingList.remove(idx);
307 - (void)addObjectsFromArray:(NSArray *)array {
308 changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(self.count, array.count), ^{
309 RLMAccessorContext context(_realm, *_objectInfo);
310 for (id obj in array) {
311 RLMArrayValidateMatchingObjectType(self, obj);
312 _backingList.add(context, obj);
317 - (void)removeAllObjects {
318 changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, self.count), ^{
319 _backingList.remove_all();
323 - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)object {
324 RLMArrayValidateMatchingObjectType(self, object);
325 changeArray(self, NSKeyValueChangeReplacement, index, ^{
326 RLMAccessorContext context(_realm, *_objectInfo);
327 _backingList.set(context, index, object);
331 - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
332 auto start = std::min(sourceIndex, destinationIndex);
333 auto len = std::max(sourceIndex, destinationIndex) - start + 1;
334 changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
335 _backingList.move(sourceIndex, destinationIndex);
339 - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
340 changeArray(self, NSKeyValueChangeReplacement, ^{
341 _backingList.swap(index1, index2);
343 NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
344 [set addIndex:index2];
349 - (NSUInteger)indexOfObject:(id)object {
350 RLMArrayValidateMatchingObjectType(self, object);
351 return translateErrors([&] {
352 RLMAccessorContext context(_realm, *_objectInfo);
353 return RLMConvertNotFound(_backingList.find(context, object));
357 - (id)valueForKeyPath:(NSString *)keyPath {
358 if ([keyPath hasPrefix:@"@"]) {
359 // Delegate KVC collection operators to RLMResults
360 return translateErrors([&] {
361 auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.as_results()];
362 return [results valueForKeyPath:keyPath];
365 return [super valueForKeyPath:keyPath];
368 - (id)valueForKey:(NSString *)key {
369 // Ideally we'd use "@invalidated" for this so that "invalidated" would use
370 // normal array KVC semantics, but observing @things works very oddly (when
371 // it's part of a key path, it's triggered automatically when array index
372 // changes occur, and can't be sent explicitly, but works normally when it's
373 // the entire key path), and an RLMManagedArray *can't* have objects where
374 // invalidated is true, so we're not losing much.
375 return translateErrors([&]() -> id {
376 if ([key isEqualToString:RLMInvalidatedKey]) {
377 return @(!_backingList.is_valid());
380 _backingList.verify_attached();
381 return RLMCollectionValueForKey(_backingList, key, _realm, *_objectInfo);
385 - (void)setValue:(id)value forKey:(NSString *)key {
386 if ([key isEqualToString:@"self"]) {
387 RLMArrayValidateMatchingObjectType(self, value);
388 RLMAccessorContext context(_realm, *_objectInfo);
389 translateErrors([&] {
390 for (size_t i = 0, count = _backingList.size(); i < count; ++i) {
391 _backingList.set(context, i, value);
396 else if (_type == RLMPropertyTypeObject) {
397 RLMArrayValidateMatchingObjectType(self, value);
398 translateErrors([&] { _backingList.verify_in_transaction(); });
399 RLMCollectionSetValueForKey(self, key, value);
402 [self setValue:value forUndefinedKey:key];
406 - (size_t)columnForProperty:(NSString *)propertyName {
407 if (_backingList.get_type() == realm::PropertyType::Object) {
408 return _objectInfo->tableColumn(propertyName);
410 if (![propertyName isEqualToString:@"self"]) {
411 @throw RLMException(@"Arrays of '%@' can only be aggregated on \"self\"", RLMTypeToString(_type));
416 - (id)minOfProperty:(NSString *)property {
417 size_t column = [self columnForProperty:property];
418 auto value = translateErrors(self, [&] { return _backingList.min(column); }, @"minOfProperty");
419 return value ? RLMMixedToObjc(*value) : nil;
422 - (id)maxOfProperty:(NSString *)property {
423 size_t column = [self columnForProperty:property];
424 auto value = translateErrors(self, [&] { return _backingList.max(column); }, @"maxOfProperty");
425 return value ? RLMMixedToObjc(*value) : nil;
428 - (id)sumOfProperty:(NSString *)property {
429 size_t column = [self columnForProperty:property];
430 return RLMMixedToObjc(translateErrors(self, [&] { return _backingList.sum(column); }, @"sumOfProperty"));
433 - (id)averageOfProperty:(NSString *)property {
434 size_t column = [self columnForProperty:property];
435 auto value = translateErrors(self, [&] { return _backingList.average(column); }, @"averageOfProperty");
436 return value ? @(*value) : nil;
439 - (void)deleteObjectsFromRealm {
440 if (_type != RLMPropertyTypeObject) {
441 @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.", RLMTypeToString(_type));
443 // delete all target rows from the realm
444 RLMTrackDeletions(_realm, ^{
445 translateErrors([&] { _backingList.delete_all(); });
449 - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
450 return translateErrors([&] {
451 return [RLMResults resultsWithObjectInfo:*_objectInfo
452 results:_backingList.sort(RLMSortDescriptorsToKeypathArray(properties))];
456 - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
457 if (_type != RLMPropertyTypeObject) {
458 @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
460 auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group);
461 auto results = translateErrors([&] { return _backingList.filter(std::move(query)); });
462 return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)];
465 - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
466 if (_type != RLMPropertyTypeObject) {
467 @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
469 realm::Query query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema,
470 _realm.schema, _realm.group);
472 return translateErrors([&] {
473 return RLMConvertNotFound(_backingList.find(std::move(query)));
477 - (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes {
478 // FIXME: this is called by KVO when array changes are made. It's not clear
479 // why, and returning nil seems to work fine.
483 - (void)addObserver:(id)observer
484 forKeyPath:(NSString *)keyPath
485 options:(NSKeyValueObservingOptions)options
486 context:(void *)context {
487 RLMEnsureArrayObservationInfo(_observationInfo, keyPath, self, self);
488 [super addObserver:observer forKeyPath:keyPath options:options context:context];
491 - (realm::TableView)tableView {
492 return translateErrors([&] { return _backingList.get_query(); }).find_all();
495 - (RLMFastEnumerator *)fastEnumerator {
496 return translateErrors([&] {
497 return [[RLMFastEnumerator alloc] initWithList:_backingList collection:self
498 realm:_realm classInfo:*_objectInfo];
502 // The compiler complains about the method's argument type not matching due to
503 // it not having the generic type attached, but it doesn't seem to be possible
504 // to actually include the generic type
505 // http://www.openradar.me/radar?id=6135653276319744
506 #pragma clang diagnostic push
507 #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
508 - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
509 [_realm verifyNotificationsAreSupported:true];
510 return RLMAddNotificationBlock(self, _backingList, block);
512 #pragma clang diagnostic pop
514 #pragma mark - Thread Confined Protocol Conformance
516 - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
517 auto list_reference = _realm->_realm->obtain_thread_safe_reference(_backingList);
518 return std::make_unique<realm::ThreadSafeReference<realm::List>>(std::move(list_reference));
521 - (RLMManagedArrayHandoverMetadata *)objectiveCMetadata {
522 RLMManagedArrayHandoverMetadata *metadata = [[RLMManagedArrayHandoverMetadata alloc] init];
523 metadata.parentClassName = _ownerInfo->rlmObjectSchema.className;
528 + (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
529 metadata:(RLMManagedArrayHandoverMetadata *)metadata
530 realm:(RLMRealm *)realm {
531 REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<realm::List> *>(reference.get()));
532 auto list_reference = static_cast<realm::ThreadSafeReference<realm::List> *>(reference.get());
534 auto list = realm->_realm->resolve_thread_safe_reference(std::move(*list_reference));
535 if (!list.is_valid()) {
538 RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName];
539 return [[RLMManagedArray alloc] initWithList:std::move(list)
541 parentInfo:parentInfo
542 property:parentInfo->rlmObjectSchema[metadata.key]];