You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
545 lines
20 KiB
545 lines
20 KiB
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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
|
|
realm:(__unsafe_unretained RLMRealm *const)realm
|
|
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 = 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())
|
|
realm:realm
|
|
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(_realm, *_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->_realm, *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(_realm, *_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(_realm, *_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(_realm, *_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(_realm, *_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, _realm, *_objectInfo);
|
|
});
|
|
}
|
|
|
|
- (void)setValue:(id)value forKey:(NSString *)key {
|
|
if ([key isEqualToString:@"self"]) {
|
|
RLMArrayValidateMatchingObjectType(self, value);
|
|
RLMAccessorContext context(_realm, *_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
|
|
realm:_realm 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)
|
|
realm:realm
|
|
parentInfo:parentInfo
|
|
property:parentInfo->rlmObjectSchema[metadata.key]];
|
|
}
|
|
|
|
@end
|
|
|