Newer
Older
bremer-ios-app / Pods / Realm / Realm / RLMManagedSet.mm
yhornisse on 10 Sep 2023 19 KB Initial Commit
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2020 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 "RLMSet_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 "RLMSectionedResults_Private.hpp"
#import "RLMThreadSafeReference_Private.hpp"
#import "RLMUtil.hpp"

#import <realm/collection.hpp>
#import <realm/object-store/set.hpp>
#import <realm/set.hpp>

#import <realm/object-store/results.hpp>
#import <realm/object-store/shared_realm.hpp>

@interface RLMManagedSetHandoverMetadata : NSObject
@property (nonatomic) NSString *parentClassName;
@property (nonatomic) NSString *key;
@end

@implementation RLMManagedSetHandoverMetadata
@end

@interface RLMManagedSet () <RLMThreadConfined_Private>
@end

//
// RLMSet implementation
//
@implementation RLMManagedSet {
@public
    realm::object_store::Set _backingSet;
    RLMRealm *_realm;
    RLMClassInfo *_objectInfo;
    RLMClassInfo *_ownerInfo;
    std::unique_ptr<RLMObservationInfo> _observationInfo;
}

- (RLMManagedSet *)initWithBackingCollection:(realm::object_store::Set)set
                                  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(set.get_realm() == _realm->_realm);
        _backingSet = std::move(set);
        _ownerInfo = parentInfo;
        if (property.type == RLMPropertyTypeObject)
            _objectInfo = &parentInfo->linkTargetType(property.index);
        else
            _objectInfo = _ownerInfo;
        _key = property.name;
    }
    return self;
}

- (RLMManagedSet *)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);
    return [self initWithBackingCollection:realm::object_store::Set(realm->_realm, parentObject->_row, col)
                                parentInfo:parentObject->_info
                                  property:property];
}

- (RLMManagedSet *)initWithParent:(realm::Obj)parent
                         property:(__unsafe_unretained RLMProperty *const)property
                       parentInfo:(RLMClassInfo&)info {
    auto col = info.tableColumn(property);
    return [self initWithBackingCollection:realm::object_store::Set(info.realm->_realm, parent, col)
                                parentInfo:&info
                                  property:property];
}

void RLMValidateSetObservationKey(__unsafe_unretained NSString *const keyPath,
                                  __unsafe_unretained RLMSet *const set) {
    if (![keyPath isEqualToString:RLMInvalidatedKey]) {
        @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@",
                            [set class], set, keyPath);
    }
}

void RLMEnsureSetObservationInfo(std::unique_ptr<RLMObservationInfo>& info,
                                   __unsafe_unretained NSString *const keyPath,
                                   __unsafe_unretained RLMSet *const set,
                                   __unsafe_unretained id const observed) {
    RLMValidateSetObservationKey(keyPath, set);
    if (!info && set.class == [RLMManagedSet class]) {
        auto lv = static_cast<RLMManagedSet *>(set);
        info = std::make_unique<RLMObservationInfo>(*lv->_ownerInfo,
                                                    lv->_backingSet.get_parent_object_key(),
                                                    observed);
    }
}

template<typename Function>
__attribute__((always_inline))
static auto translateErrors(Function&& f) {
    return translateCollectionError(static_cast<Function&&>(f), @"Set");
}

static void changeSet(__unsafe_unretained RLMManagedSet *const set,
                      dispatch_block_t f) {
    translateErrors([&] { set->_backingSet.verify_in_transaction(); });

    RLMObservationTracker tracker(set->_realm, false);
    tracker.trackDeletions();
    auto obsInfo = RLMGetObservationInfo(set->_observationInfo.get(),
                                         set->_backingSet.get_parent_object_key(),
                                         *set->_ownerInfo);
    if (obsInfo) {
        tracker.willChange(obsInfo, set->_key);
    }

    translateErrors(f);
}

//
// public method implementations
//
- (RLMRealm *)realm {
    return _realm;
}

- (NSUInteger)count {
    return translateErrors([&] { return _backingSet.size(); });
}

- (NSArray<id> *)allObjects {
    NSMutableArray *arr = [NSMutableArray new];
    for (id prop : self) {
        [arr addObject:prop];
    }
    return arr;
}

- (BOOL)isInvalidated {
    return translateErrors([&] { return !_backingSet.is_valid(); });
}

- (RLMClassInfo *)objectInfo {
    return _objectInfo;
}


- (bool)isBackedBySet:(realm::object_store::Set const&)set {
    return _backingSet == set;
}

- (BOOL)isEqual:(id)object {
    return [object respondsToSelector:@selector(isBackedBySet:)] && [object isBackedBySet:_backingSet];
}

- (NSUInteger)hash {
    return std::hash<realm::object_store::Set>()(_backingSet);
}

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
                                  objects:(__unused __unsafe_unretained id [])buffer
                                    count:(NSUInteger)len {
    return RLMFastEnumerate(state, len, self);
}

static void RLMInsertObject(RLMManagedSet *set, id object) {
    RLMSetValidateMatchingObjectType(set, object);
    changeSet(set, ^{
        RLMAccessorContext context(*set->_objectInfo);
        set->_backingSet.insert(context, object);
    });
}

static void RLMRemoveObject(RLMManagedSet *set, id object) {
    RLMSetValidateMatchingObjectType(set, object);
    changeSet(set, ^{
        RLMAccessorContext context(*set->_objectInfo);
        set->_backingSet.remove(context, object);
    });
}

static void ensureInWriteTransaction(NSString *message, RLMManagedSet *set, RLMManagedSet *otherSet) {
    if (!set.realm.inWriteTransaction && !otherSet.realm.inWriteTransaction) {
        @throw RLMException(@"Can only perform %@ in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.", message);
    }
}

- (void)addObject:(id)object {
    RLMInsertObject(self, object);
}

- (void)addObjects:(id<NSFastEnumeration>)objects {
    changeSet(self, ^{
        RLMAccessorContext context(*_objectInfo);
        for (id obj in objects) {
            RLMSetValidateMatchingObjectType(self, obj);
            _backingSet.insert(context, obj);
        }
    });
}

- (void)removeObject:(id)object {
    RLMRemoveObject(self, object);
}

- (void)removeAllObjects {
    changeSet(self, ^{
        _backingSet.remove_all();
    });
}

- (void)replaceAllObjectsWithObjects:(NSArray *)objects {
    changeSet(self, ^{
        RLMAccessorContext context(*_objectInfo);
        _backingSet.assign(context, objects);
    });
}

- (RLMManagedSet *)managedObjectFrom:(RLMSet *)set {
    auto managedSet = RLMDynamicCast<RLMManagedSet>(set);
    if (!managedSet) {
        @throw RLMException(@"Right hand side value must be a managed Set.");
    }
    if (_type != managedSet->_type) {
        @throw RLMException(@"Cannot intersect sets of type '%@' and '%@'.",
                            RLMTypeToString(_type), RLMTypeToString(managedSet->_type));
    }
    if (_realm != managedSet->_realm) {
        @throw RLMException(@"Cannot insersect sets managed by different Realms.");
    }
    if (_objectInfo != managedSet->_objectInfo) {
        @throw RLMException(@"Cannot intersect sets of type '%@' and '%@'.",
                            _objectInfo->rlmObjectSchema.className,
                            managedSet->_objectInfo->rlmObjectSchema.className);

    }
    return managedSet;
}

- (BOOL)isSubsetOfSet:(RLMSet<id> *)set {
    RLMManagedSet *rhs = [self managedObjectFrom:set];
    return _backingSet.is_subset_of(rhs->_backingSet);
}

- (BOOL)intersectsSet:(RLMSet<id> *)set {
    RLMManagedSet *rhs = [self managedObjectFrom:set];
    return _backingSet.intersects(rhs->_backingSet);
}

- (BOOL)containsObject:(id)obj {
    RLMSetValidateMatchingObjectType(self, obj);
    RLMAccessorContext context(*_objectInfo);
    auto r = _backingSet.find(context, obj);
    return r != realm::npos;
}

- (BOOL)isEqualToSet:(RLMSet<id> *)set {
    RLMManagedSet *rhs = [self managedObjectFrom:set];
    return [self isEqual:rhs];
}

- (void)setSet:(RLMSet<id> *)set {
    RLMManagedSet *rhs = [self managedObjectFrom:set];
    ensureInWriteTransaction(@"[RLMSet setSet:]", self, rhs);
    changeSet(self, ^{
        RLMAccessorContext context(*_objectInfo);
        _backingSet.assign(context, rhs);
    });
}

- (void)intersectSet:(RLMSet<id> *)set {
    RLMManagedSet *rhs = [self managedObjectFrom:set];
    ensureInWriteTransaction(@"[RLMSet intersectSet:]", self, rhs);
    changeSet(self, ^{
        _backingSet.assign_intersection(rhs->_backingSet);
    });
}

- (void)unionSet:(RLMSet<id> *)set {
    RLMManagedSet *rhs = [self managedObjectFrom:set];
    ensureInWriteTransaction(@"[RLMSet unionSet:]", self, rhs);
    changeSet(self, ^{
        _backingSet.assign_union(rhs->_backingSet);
    });
}

- (void)minusSet:(RLMSet<id> *)set {
    RLMManagedSet *rhs = [self managedObjectFrom:set];
    ensureInWriteTransaction(@"[RLMSet minusSet:]", self, rhs);
    changeSet(self, ^{
        _backingSet.assign_difference(rhs->_backingSet);
    });
}

- (id)objectAtIndex:(NSUInteger)index {
    return translateErrors([&] {
        RLMAccessorContext context(*_objectInfo);
        return _backingSet.get(context, index);
    });
}

- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
    size_t count = self.count;
    NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:indexes.count];
    RLMAccessorContext context(*_objectInfo);
    for (NSUInteger i = indexes.firstIndex; i != NSNotFound; i = [indexes indexGreaterThanIndex:i]) {
        if (i >= count) {
            return nil;
        }
        [result addObject:_backingSet.get(context, i)];
    }
    return result;
}

- (id)firstObject {
    return translateErrors([&] {
        RLMAccessorContext context(*_objectInfo);
        return _backingSet.size() ? _backingSet.get(context, 0) : nil;
    });
}

- (id)lastObject {
    return translateErrors([&] {
        RLMAccessorContext context(*_objectInfo);
        size_t size = _backingSet.size();
        return size ? _backingSet.get(context, size - 1) : nil;
    });
}

- (id)valueForKeyPath:(NSString *)keyPath {
    if ([keyPath hasPrefix:@"@"]) {
        // Delegate KVC collection operators to RLMResults
        return translateErrors([&] {
            auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingSet.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 RLMManagedSet *can't* have objects where
    // invalidated is true, so we're not losing much.
    return translateErrors([&]() -> id {
        if ([key isEqualToString:RLMInvalidatedKey]) {
            return @(!_backingSet.is_valid());
        }

        _backingSet.verify_attached();
        return  [NSSet setWithArray:RLMCollectionValueForKey(_backingSet, key, *_objectInfo)];
    });
    return nil;
}

- (void)setValue:(id)value forKey:(NSString *)key {
    if ([key isEqualToString:@"self"]) {
        RLMSetValidateMatchingObjectType(self, value);
        RLMAccessorContext context(*_objectInfo);
        translateErrors([&] {
            _backingSet.remove_all();
            _backingSet.insert(context, value);
            return;
        });
    } else if (_type == RLMPropertyTypeObject) {
        RLMSetValidateMatchingObjectType(self, value);
        translateErrors([&] { _backingSet.verify_in_transaction(); });
        RLMCollectionSetValueForKey(self, key, value);
    }
    else {
        [self setValue:value forUndefinedKey:key];
    }
}

- (id)minOfProperty:(NSString *)property {
    auto column = columnForProperty(property, _backingSet, _objectInfo, _type, RLMCollectionTypeSet);
    auto value = translateErrors([&] { return _backingSet.min(column); });
    return value ? RLMMixedToObjc(*value) : nil;
}

- (id)maxOfProperty:(NSString *)property {
    auto column = columnForProperty(property, _backingSet, _objectInfo, _type, RLMCollectionTypeSet);
    auto value = translateErrors([&] { return _backingSet.max(column); });
    return value ? RLMMixedToObjc(*value) : nil;
}

- (id)sumOfProperty:(NSString *)property {
    auto column = columnForProperty(property, _backingSet, _objectInfo, _type, RLMCollectionTypeSet);
    return RLMMixedToObjc(translateErrors([&] { return _backingSet.sum(column); }));
}

- (id)averageOfProperty:(NSString *)property {
    auto column = columnForProperty(property, _backingSet, _objectInfo, _type, RLMCollectionTypeSet);
    auto value = translateErrors([&] { return _backingSet.average(column); });
    return value ? RLMMixedToObjc(*value) : nil;
}

- (void)deleteObjectsFromRealm {
    if (_type != RLMPropertyTypeObject) {
        @throw RLMException(@"Cannot delete objects from RLMSet<%@>: only RLMObjects can be deleted.", RLMTypeToString(_type));
    }
    // delete all target rows from the realm
    RLMObservationTracker tracker(_realm, true);
    translateErrors([&] { _backingSet.delete_all(); });
}

- (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
    return translateErrors([&] {
        return [RLMResults  resultsWithObjectInfo:*_objectInfo
                                          results:_backingSet.sort(RLMSortDescriptorsToKeypathArray(properties))];
    });
}

- (RLMResults *)distinctResultsUsingKeyPaths:(NSArray<NSString *> *)keyPaths {
    return translateErrors([&] {
        auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingSet.as_results()];
        return [results distinctResultsUsingKeyPaths:keyPaths];
    });
}

- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
    if (_type != RLMPropertyTypeObject) {
        @throw RLMException(@"Querying is currently only implemented for sets of Realm Objects");
    }
    auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group);
    auto results = translateErrors([&] { return _backingSet.filter(std::move(query)); });
    return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)];
}

- (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath
                                                  ascending:(BOOL)ascending
                                                   keyBlock:(RLMSectionedResultsKeyBlock)keyBlock {
    return [[RLMSectionedResults alloc] initWithResults:[self sortedResultsUsingKeyPath:keyPath ascending:ascending]
                                               keyBlock:keyBlock];
}

- (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray<RLMSortDescriptor *> *)sortDescriptors
                                                     keyBlock:(RLMSectionedResultsKeyBlock)keyBlock {
    return [[RLMSectionedResults alloc] initWithResults:[self sortedResultsUsingDescriptors:sortDescriptors]
                                               keyBlock:keyBlock];
}

- (void)addObserver:(id)observer
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context {
    RLMEnsureSetObservationInfo(_observationInfo, keyPath, self, self);
    [super addObserver:observer forKeyPath:keyPath options:options context:context];
}

- (RLMFastEnumerator *)fastEnumerator {
    return translateErrors([&] {
        return [[RLMFastEnumerator alloc] initWithBackingCollection:_backingSet
                                                         collection:self
                                                          classInfo:*_objectInfo];
    });
}

- (realm::TableView)tableView {
    return translateErrors([&] { return _backingSet.get_query(); }).find_all();
}

- (BOOL)isFrozen {
    return _realm.isFrozen;
}

- (instancetype)resolveInRealm:(RLMRealm *)realm {
    auto& parentInfo = _ownerInfo->resolve(realm);
    return translateErrors([&] {
        return [[self.class alloc] initWithBackingCollection:_backingSet.freeze(realm->_realm)
                                                  parentInfo:&parentInfo
                                                    property:parentInfo.rlmObjectSchema[_key]];
    });
}

- (instancetype)freeze {
    if (self.frozen) {
        return self;
    }
    return [self resolveInRealm:_realm.freeze];
}

- (instancetype)thaw {
    if (!self.frozen) {
        return self;
    }
    return [self resolveInRealm:_realm.thaw];
}

- (realm::NotificationToken)addNotificationCallback:(id)block
keyPaths:(std::optional<std::vector<std::vector<std::pair<realm::TableKey, realm::ColKey>>>>&&)keyPaths {
    return _backingSet.add_notification_callback(RLMWrapCollectionChangeCallback(block, self, false), std::move(keyPaths));
}

#pragma mark - Thread Confined Protocol Conformance

- (realm::ThreadSafeReference)makeThreadSafeReference {
    return _backingSet;
}

- (RLMManagedSetHandoverMetadata *)objectiveCMetadata {
    RLMManagedSetHandoverMetadata *metadata = [[RLMManagedSetHandoverMetadata alloc] init];
    metadata.parentClassName = _ownerInfo->rlmObjectSchema.className;
    metadata.key = _key;
    return metadata;
}

+ (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference
                                     metadata:(RLMManagedSetHandoverMetadata *)metadata
                                        realm:(RLMRealm *)realm {
    auto set = reference.resolve<realm::object_store::Set>(realm->_realm);
    if (!set.is_valid()) {
        return nil;
    }
    RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName];
    return [[RLMManagedSet alloc] initWithBackingCollection:std::move(set)
                                                 parentInfo:parentInfo
                                                   property:parentInfo->rlmObjectSchema[metadata.key]];
}

@end