Newer
Older
bremer-ios-app / Pods / Realm / include / RLMObservation.hpp
yhornisse on 10 Sep 2023 7 KB Initial Commit
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 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 <Foundation/Foundation.h>

#import <realm/obj.hpp>
#import <realm/object-store/binding_context.hpp>
#import <realm/object-store/impl/deep_change_checker.hpp>
#import <realm/table.hpp>

@class RLMObjectBase, RLMRealm, RLMSchema, RLMProperty, RLMObjectSchema;
class RLMClassInfo;
class RLMSchemaInfo;

namespace realm {
    class History;
    class SharedGroup;
    struct TableKey;
    struct ColKey;
}

// RLMObservationInfo stores all of the KVO-related data for RLMObjectBase and
// RLMSet/Array. There is a one-to-one relationship between observed objects and
// RLMObservationInfo instances, so it could be folded into RLMObjectBase, and
// is a separate class mostly to avoid making all accessor objects far larger.
//
// RLMClassInfo stores a vector of pointers to the first observation info
// created for each row. If there are multiple observation infos for a single
// row (such as if there are multiple observed objects backed by a single row,
// or if both an object and an array property of that object are observed),
// they're stored in an intrusive doubly-linked-list in the `next` and `prev`
// members. This is done primarily to make it simpler and faster to loop over
// all of the observed objects for a single row, as that needs to be done for
// every change.
class RLMObservationInfo {
public:
    RLMObservationInfo(id object);
    RLMObservationInfo(RLMClassInfo &objectSchema, realm::ObjKey row, id object);
    ~RLMObservationInfo();

    realm::Obj const& getRow() const {
        return row;
    }

    NSString *columnName(realm::ColKey col) const noexcept;

    // Send willChange/didChange notifications to all observers for this object/row
    // Sends the array versions if indexes is non-nil, normal versions otherwise
    void willChange(NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil) const;
    void didChange(NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil) const;

    bool isForRow(realm::ObjKey key) const {
        return row.get_key() == key;
    }

    void recordObserver(realm::Obj& row, RLMClassInfo *objectInfo, RLMObjectSchema *objectSchema, NSString *keyPath);
    void removeObserver();
    bool hasObservers() const { return observerCount > 0; }

    // valueForKey: on observed object and array properties needs to return the
    // same object each time for KVO to work at all. Doing this all the time
    // requires some odd semantics to avoid reference cycles, so instead we do
    // it only to the extent specifically required by KVO. In addition, we
    // need to continue to return the same object even if this row is deleted,
    // or deleting an object with active observers will explode horribly.
    // Once prepareForInvalidation() is called, valueForKey() will always return
    // the cached value for object and array properties without checking the
    // backing row to verify it's up-to-date.
    //
    // prepareForInvalidation() must be called on the head of the linked list
    // (i.e. on the object pointed to directly by the object schema)
    id valueForKey(NSString *key);

    void prepareForInvalidation();

private:
    // Doubly-linked-list of observed objects for the same row as this
    RLMObservationInfo *next = nullptr;
    RLMObservationInfo *prev = nullptr;

    // Row being observed
    realm::Obj row;
    RLMClassInfo *objectSchema = nullptr;

    // Object doing the observing
    __unsafe_unretained id object = nil;

    // valueForKey: hack
    bool invalidated = false;
    size_t observerCount = 0;
    NSString *lastKey = nil;
    __unsafe_unretained RLMProperty *lastProp = nil;

    // objects returned from valueForKey() to keep them alive in case observers
    // are added and so that they can still be accessed after row is detached
    NSMutableDictionary *cachedObjects;

    void setRow(realm::Table const& table, realm::ObjKey newRow);

    template<typename F>
    void forEach(F&& f) const {
        // The user's observation handler may release their last reference to
        // the object being observed, which will result in the RLMObservationInfo
        // being destroyed. As a result, we need to retain the object which owns
        // both `this` and the current info we're looking at.
        __attribute__((objc_precise_lifetime)) id self = object, current;
        for (auto info = prev; info; info = info->prev) {
            current = info->object;
            f(info->object);
        }
        for (auto info = this; info; info = info->next) {
            current = info->object;
            f(info->object);
        }
    }

    // Default move/copy constructors don't work due to the intrusive linked
    // list and we don't need them
    RLMObservationInfo(RLMObservationInfo const&) = delete;
    RLMObservationInfo(RLMObservationInfo&&) = delete;
    RLMObservationInfo& operator=(RLMObservationInfo const&) = delete;
    RLMObservationInfo& operator=(RLMObservationInfo&&) = delete;
};

// Get the the observation info chain for the given row
// Will simply return info if it's non-null, and will search ojectSchema's array
// for a matching one otherwise, and return null if there are none
RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info, realm::ObjKey row, RLMClassInfo& objectSchema);

// delete all objects from a single table with change notifications
void RLMClearTable(RLMClassInfo &realm);

class RLMObservationTracker {
public:
    RLMObservationTracker(RLMRealm *realm, bool trackDeletions=false);
    ~RLMObservationTracker();

    void trackDeletions();

    void willChange(RLMObservationInfo *info, NSString *key,
                    NSKeyValueChange kind=NSKeyValueChangeSetting,
                    NSIndexSet *indexes=nil);
    void didChange();

private:
    std::vector<std::vector<RLMObservationInfo *> *> _observedTables;
    __unsafe_unretained RLMRealm const*_realm;
    realm::Group& _group;
    RLMObservationInfo *_info = nullptr;

    NSString *_key;
    NSKeyValueChange _kind = NSKeyValueChangeSetting;
    NSIndexSet *_indexes;

    struct Change {
        RLMObservationInfo *info;
        __unsafe_unretained NSString *property;
        NSMutableIndexSet *indexes;
    };
    std::vector<Change> _changes;
    std::vector<RLMObservationInfo *> _invalidated;

    template<typename CascadeNotification>
    void cascadeNotification(CascadeNotification const&);
};

std::vector<realm::BindingContext::ObserverState> RLMGetObservedRows(RLMSchemaInfo const& schema);
void RLMWillChange(std::vector<realm::BindingContext::ObserverState> const& observed, std::vector<void *> const& invalidated);
void RLMDidChange(std::vector<realm::BindingContext::ObserverState> const& observed, std::vector<void *> const& invalidated);

// Used for checking if an `Object` declared with `@StateRealmObject` needs to have
// it's accessors temporarily removed and added back so that the `Object` can be
// managed be the Realm.
[[clang::objc_runtime_visible]]
@interface RLMSwiftUIKVO : NSObject
+ (BOOL)removeObserversFromObject:(NSObject *)object;
+ (void)addObserversToObject:(NSObject *)object;
@end