123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // 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 "RLMAccessor.hpp"
- #import "RLMObjectSchema_Private.hpp"
- #import "RLMObjectStore.h"
- #import "RLMObject_Private.hpp"
- #import "RLMObservation.hpp"
- #import "RLMProperty_Private.h"
- #import "RLMQueryUtil.hpp"
- #import "RLMRealm_Private.hpp"
- #import "RLMSchema.h"
- #import "RLMThreadSafeReference_Private.hpp"
- #import "RLMUtil.hpp"
- #import "list.hpp"
- #import "results.hpp"
- #import "shared_realm.hpp"
- #import <realm/table_view.hpp>
- #import <objc/runtime.h>
- @interface RLMManagedArrayHandoverMetadata : NSObject
- @property (nonatomic) NSString *parentClassName;
- @property (nonatomic) NSString *key;
- @end
- @implementation RLMManagedArrayHandoverMetadata
- @end
- @interface RLMManagedArray () <RLMThreadConfined_Private>
- @end
- //
- // RLMArray implementation
- //
- @implementation RLMManagedArray {
- @public
- realm::List _backingList;
- RLMRealm *_realm;
- RLMClassInfo *_objectInfo;
- RLMClassInfo *_ownerInfo;
- std::unique_ptr<RLMObservationInfo> _observationInfo;
- }
- - (RLMManagedArray *)initWithList:(realm::List)list
- parentInfo:(RLMClassInfo *)parentInfo
- property:(__unsafe_unretained RLMProperty *const)property {
- if (property.type == RLMPropertyTypeObject)
- self = [self initWithObjectClassName:property.objectClassName];
- else
- self = [self initWithObjectType:property.type optional:property.optional];
- if (self) {
- _realm = parentInfo->realm;
- REALM_ASSERT(list.get_realm() == _realm->_realm);
- _backingList = std::move(list);
- _ownerInfo = parentInfo;
- if (property.type == RLMPropertyTypeObject)
- _objectInfo = &parentInfo->linkTargetType(property.index);
- else
- _objectInfo = _ownerInfo;
- _key = property.name;
- }
- return self;
- }
- - (RLMManagedArray *)initWithParent:(__unsafe_unretained RLMObjectBase *const)parentObject
- property:(__unsafe_unretained RLMProperty *const)property {
- __unsafe_unretained RLMRealm *const realm = parentObject->_realm;
- auto col = parentObject->_info->tableColumn(property);
- auto& row = parentObject->_row;
- return [self initWithList:realm::List(realm->_realm, *row.get_table(), col, row.get_index())
- parentInfo:parentObject->_info
- property:property];
- }
- void RLMValidateArrayObservationKey(__unsafe_unretained NSString *const keyPath,
- __unsafe_unretained RLMArray *const array) {
- if (![keyPath isEqualToString:RLMInvalidatedKey]) {
- @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@",
- [array class], array, keyPath);
- }
- }
- void RLMEnsureArrayObservationInfo(std::unique_ptr<RLMObservationInfo>& info,
- __unsafe_unretained NSString *const keyPath,
- __unsafe_unretained RLMArray *const array,
- __unsafe_unretained id const observed) {
- RLMValidateArrayObservationKey(keyPath, array);
- if (!info && array.class == [RLMManagedArray class]) {
- auto lv = static_cast<RLMManagedArray *>(array);
- info = std::make_unique<RLMObservationInfo>(*lv->_ownerInfo,
- lv->_backingList.get_origin_row_index(),
- observed);
- }
- }
- //
- // validation helpers
- //
- [[gnu::noinline]]
- [[noreturn]]
- static void throwError(__unsafe_unretained RLMManagedArray *const ar, NSString *aggregateMethod) {
- try {
- throw;
- }
- catch (realm::InvalidTransactionException const&) {
- @throw RLMException(@"Cannot modify managed RLMArray outside of a write transaction.");
- }
- catch (realm::IncorrectThreadException const&) {
- @throw RLMException(@"Realm accessed from incorrect thread.");
- }
- catch (realm::List::InvalidatedException const&) {
- @throw RLMException(@"RLMArray has been invalidated or the containing object has been deleted.");
- }
- catch (realm::List::OutOfBoundsIndexException const& e) {
- @throw RLMException(@"Index %zu is out of bounds (must be less than %zu).",
- e.requested, e.valid_count);
- }
- catch (realm::Results::UnsupportedColumnTypeException const& e) {
- if (ar->_backingList.get_type() == realm::PropertyType::Object) {
- @throw RLMException(@"%@: is not supported for %s%s property '%s'.",
- aggregateMethod,
- string_for_property_type(e.property_type),
- is_nullable(e.property_type) ? "?" : "",
- e.column_name.data());
- }
- @throw RLMException(@"%@: is not supported for %s%s array '%@.%@'.",
- aggregateMethod,
- string_for_property_type(e.property_type),
- is_nullable(e.property_type) ? "?" : "",
- ar->_ownerInfo->rlmObjectSchema.className, ar->_key);
- }
- catch (std::logic_error const& e) {
- @throw RLMException(e);
- }
- }
- template<typename Function>
- static auto translateErrors(__unsafe_unretained RLMManagedArray *const ar,
- Function&& f, NSString *aggregateMethod=nil) {
- try {
- return f();
- }
- catch (...) {
- throwError(ar, aggregateMethod);
- }
- }
- template<typename Function>
- static auto translateErrors(Function&& f) {
- try {
- return f();
- }
- catch (...) {
- throwError(nil, nil);
- }
- }
- template<typename IndexSetFactory>
- static void changeArray(__unsafe_unretained RLMManagedArray *const ar,
- NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
- translateErrors([&] { ar->_backingList.verify_in_transaction(); });
- RLMObservationInfo *info = RLMGetObservationInfo(ar->_observationInfo.get(),
- ar->_backingList.get_origin_row_index(),
- *ar->_ownerInfo);
- if (info) {
- NSIndexSet *indexes = is();
- info->willChange(ar->_key, kind, indexes);
- try {
- f();
- }
- catch (...) {
- info->didChange(ar->_key, kind, indexes);
- throwError(ar, nil);
- }
- info->didChange(ar->_key, kind, indexes);
- }
- else {
- translateErrors([&] { f(); });
- }
- }
- static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) {
- changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
- }
- static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) {
- changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
- }
- static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) {
- changeArray(ar, kind, f, [=] { return is; });
- }
- //
- // public method implementations
- //
- - (RLMRealm *)realm {
- return _realm;
- }
- - (NSUInteger)count {
- return translateErrors([&] { return _backingList.size(); });
- }
- - (BOOL)isInvalidated {
- return translateErrors([&] { return !_backingList.is_valid(); });
- }
- - (RLMClassInfo *)objectInfo {
- return _objectInfo;
- }
- - (bool)isBackedByList:(realm::List const&)list {
- return _backingList == list;
- }
- - (BOOL)isEqual:(id)object {
- return [object respondsToSelector:@selector(isBackedByList:)] && [object isBackedByList:_backingList];
- }
- - (NSUInteger)hash {
- return std::hash<realm::List>()(_backingList);
- }
- - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
- objects:(__unused __unsafe_unretained id [])buffer
- count:(NSUInteger)len {
- return RLMFastEnumerate(state, len, self);
- }
- - (id)objectAtIndex:(NSUInteger)index {
- return translateErrors([&] {
- RLMAccessorContext context(*_objectInfo);
- return _backingList.get(context, index);
- });
- }
- static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
- RLMArrayValidateMatchingObjectType(ar, object);
- if (index == NSUIntegerMax) {
- index = translateErrors([&] { return ar->_backingList.size(); });
- }
- changeArray(ar, NSKeyValueChangeInsertion, index, ^{
- RLMAccessorContext context(*ar->_objectInfo);
- ar->_backingList.insert(context, index, object);
- });
- }
- - (void)addObject:(id)object {
- RLMInsertObject(self, object, NSUIntegerMax);
- }
- - (void)insertObject:(id)object atIndex:(NSUInteger)index {
- RLMInsertObject(self, object, index);
- }
- - (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
- changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
- NSUInteger index = [indexes firstIndex];
- RLMAccessorContext context(*_objectInfo);
- for (id obj in objects) {
- RLMArrayValidateMatchingObjectType(self, obj);
- _backingList.insert(context, index, obj);
- index = [indexes indexGreaterThanIndex:index];
- }
- });
- }
- - (void)removeObjectAtIndex:(NSUInteger)index {
- changeArray(self, NSKeyValueChangeRemoval, index, ^{
- _backingList.remove(index);
- });
- }
- - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
- changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
- [indexes enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *) {
- _backingList.remove(idx);
- }];
- });
- }
- - (void)addObjectsFromArray:(NSArray *)array {
- changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(self.count, array.count), ^{
- RLMAccessorContext context(*_objectInfo);
- for (id obj in array) {
- RLMArrayValidateMatchingObjectType(self, obj);
- _backingList.add(context, obj);
- }
- });
- }
- - (void)removeAllObjects {
- changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, self.count), ^{
- _backingList.remove_all();
- });
- }
- - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)object {
- RLMArrayValidateMatchingObjectType(self, object);
- changeArray(self, NSKeyValueChangeReplacement, index, ^{
- RLMAccessorContext context(*_objectInfo);
- _backingList.set(context, index, object);
- });
- }
- - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
- auto start = std::min(sourceIndex, destinationIndex);
- auto len = std::max(sourceIndex, destinationIndex) - start + 1;
- changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
- _backingList.move(sourceIndex, destinationIndex);
- });
- }
- - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
- changeArray(self, NSKeyValueChangeReplacement, ^{
- _backingList.swap(index1, index2);
- }, [=] {
- NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
- [set addIndex:index2];
- return set;
- });
- }
- - (NSUInteger)indexOfObject:(id)object {
- RLMArrayValidateMatchingObjectType(self, object);
- return translateErrors([&] {
- RLMAccessorContext context(*_objectInfo);
- return RLMConvertNotFound(_backingList.find(context, object));
- });
- }
- - (id)valueForKeyPath:(NSString *)keyPath {
- if ([keyPath hasPrefix:@"@"]) {
- // Delegate KVC collection operators to RLMResults
- return translateErrors([&] {
- auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.as_results()];
- return [results valueForKeyPath:keyPath];
- });
- }
- return [super valueForKeyPath:keyPath];
- }
- - (id)valueForKey:(NSString *)key {
- // Ideally we'd use "@invalidated" for this so that "invalidated" would use
- // normal array KVC semantics, but observing @things works very oddly (when
- // it's part of a key path, it's triggered automatically when array index
- // changes occur, and can't be sent explicitly, but works normally when it's
- // the entire key path), and an RLMManagedArray *can't* have objects where
- // invalidated is true, so we're not losing much.
- return translateErrors([&]() -> id {
- if ([key isEqualToString:RLMInvalidatedKey]) {
- return @(!_backingList.is_valid());
- }
- _backingList.verify_attached();
- return RLMCollectionValueForKey(_backingList, key, *_objectInfo);
- });
- }
- - (void)setValue:(id)value forKey:(NSString *)key {
- if ([key isEqualToString:@"self"]) {
- RLMArrayValidateMatchingObjectType(self, value);
- RLMAccessorContext context(*_objectInfo);
- translateErrors([&] {
- for (size_t i = 0, count = _backingList.size(); i < count; ++i) {
- _backingList.set(context, i, value);
- }
- });
- return;
- }
- else if (_type == RLMPropertyTypeObject) {
- RLMArrayValidateMatchingObjectType(self, value);
- translateErrors([&] { _backingList.verify_in_transaction(); });
- RLMCollectionSetValueForKey(self, key, value);
- }
- else {
- [self setValue:value forUndefinedKey:key];
- }
- }
- - (size_t)columnForProperty:(NSString *)propertyName {
- if (_backingList.get_type() == realm::PropertyType::Object) {
- return _objectInfo->tableColumn(propertyName);
- }
- if (![propertyName isEqualToString:@"self"]) {
- @throw RLMException(@"Arrays of '%@' can only be aggregated on \"self\"", RLMTypeToString(_type));
- }
- return 0;
- }
- - (id)minOfProperty:(NSString *)property {
- size_t column = [self columnForProperty:property];
- auto value = translateErrors(self, [&] { return _backingList.min(column); }, @"minOfProperty");
- return value ? RLMMixedToObjc(*value) : nil;
- }
- - (id)maxOfProperty:(NSString *)property {
- size_t column = [self columnForProperty:property];
- auto value = translateErrors(self, [&] { return _backingList.max(column); }, @"maxOfProperty");
- return value ? RLMMixedToObjc(*value) : nil;
- }
- - (id)sumOfProperty:(NSString *)property {
- size_t column = [self columnForProperty:property];
- return RLMMixedToObjc(translateErrors(self, [&] { return _backingList.sum(column); }, @"sumOfProperty"));
- }
- - (id)averageOfProperty:(NSString *)property {
- size_t column = [self columnForProperty:property];
- auto value = translateErrors(self, [&] { return _backingList.average(column); }, @"averageOfProperty");
- return value ? @(*value) : nil;
- }
- - (void)deleteObjectsFromRealm {
- if (_type != RLMPropertyTypeObject) {
- @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.", RLMTypeToString(_type));
- }
- // delete all target rows from the realm
- RLMTrackDeletions(_realm, ^{
- translateErrors([&] { _backingList.delete_all(); });
- });
- }
- - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
- return translateErrors([&] {
- return [RLMResults resultsWithObjectInfo:*_objectInfo
- results:_backingList.sort(RLMSortDescriptorsToKeypathArray(properties))];
- });
- }
- - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
- if (_type != RLMPropertyTypeObject) {
- @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
- }
- auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group);
- auto results = translateErrors([&] { return _backingList.filter(std::move(query)); });
- return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)];
- }
- - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
- if (_type != RLMPropertyTypeObject) {
- @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
- }
- realm::Query query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema,
- _realm.schema, _realm.group);
- return translateErrors([&] {
- return RLMConvertNotFound(_backingList.find(std::move(query)));
- });
- }
- - (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes {
- // FIXME: this is called by KVO when array changes are made. It's not clear
- // why, and returning nil seems to work fine.
- return nil;
- }
- - (void)addObserver:(id)observer
- forKeyPath:(NSString *)keyPath
- options:(NSKeyValueObservingOptions)options
- context:(void *)context {
- RLMEnsureArrayObservationInfo(_observationInfo, keyPath, self, self);
- [super addObserver:observer forKeyPath:keyPath options:options context:context];
- }
- - (realm::TableView)tableView {
- return translateErrors([&] { return _backingList.get_query(); }).find_all();
- }
- - (RLMFastEnumerator *)fastEnumerator {
- return translateErrors([&] {
- return [[RLMFastEnumerator alloc] initWithList:_backingList collection:self
- classInfo:*_objectInfo];
- });
- }
- // 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 push
- #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
- - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
- [_realm verifyNotificationsAreSupported:true];
- return RLMAddNotificationBlock(self, _backingList, block);
- }
- #pragma clang diagnostic pop
- #pragma mark - Thread Confined Protocol Conformance
- - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
- auto list_reference = _realm->_realm->obtain_thread_safe_reference(_backingList);
- return std::make_unique<realm::ThreadSafeReference<realm::List>>(std::move(list_reference));
- }
- - (RLMManagedArrayHandoverMetadata *)objectiveCMetadata {
- RLMManagedArrayHandoverMetadata *metadata = [[RLMManagedArrayHandoverMetadata alloc] init];
- metadata.parentClassName = _ownerInfo->rlmObjectSchema.className;
- metadata.key = _key;
- return metadata;
- }
- + (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
- metadata:(RLMManagedArrayHandoverMetadata *)metadata
- realm:(RLMRealm *)realm {
- REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<realm::List> *>(reference.get()));
- auto list_reference = static_cast<realm::ThreadSafeReference<realm::List> *>(reference.get());
- auto list = realm->_realm->resolve_thread_safe_reference(std::move(*list_reference));
- if (!list.is_valid()) {
- return nil;
- }
- RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName];
- return [[RLMManagedArray alloc] initWithList:std::move(list)
- parentInfo:parentInfo
- property:parentInfo->rlmObjectSchema[metadata.key]];
- }
- @end
|