//////////////////////////////////////////////////////////////////////////// // // 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 "RLMObservation.hpp" #import "RLMAccessor.h" #import "RLMArray_Private.hpp" #import "RLMListBase.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObject_Private.hpp" #import "RLMProperty_Private.h" #import "RLMRealm_Private.hpp" #import using namespace realm; namespace { template struct IteratorPair { Iterator first; Iterator second; }; template Iterator begin(IteratorPair const& p) { return p.first; } template Iterator end(IteratorPair const& p) { return p.second; } template auto reverse(Container const& c) { return IteratorPair{c.rbegin(), c.rend()}; } } RLMObservationInfo::RLMObservationInfo(RLMClassInfo &objectSchema, std::size_t row, id object) : object(object) , objectSchema(&objectSchema) { setRow(*objectSchema.table(), row); } RLMObservationInfo::RLMObservationInfo(id object) : object(object) { } RLMObservationInfo::~RLMObservationInfo() { if (prev) { // Not the head of the linked list, so just detach from the list REALM_ASSERT_DEBUG(prev->next == this); prev->next = next; if (next) { REALM_ASSERT_DEBUG(next->prev == this); next->prev = prev; } } else if (objectSchema) { // The head of the list, so remove self from the object schema's array // of observation info, either replacing self with the next info or // removing entirely if there is no next auto end = objectSchema->observedObjects.end(); auto it = find(objectSchema->observedObjects.begin(), end, this); if (it != end) { if (next) { *it = next; next->prev = nullptr; } else { iter_swap(it, std::prev(end)); objectSchema->observedObjects.pop_back(); } } } // Otherwise the observed object was unmanaged, so nothing to do #ifdef DEBUG // ensure that incorrect cleanup fails noisily object = (__bridge id)(void *)-1; prev = (RLMObservationInfo *)-1; next = (RLMObservationInfo *)-1; #endif } NSString *RLMObservationInfo::columnName(size_t col) const noexcept { return objectSchema->propertyForTableColumn(col).name; } void RLMObservationInfo::willChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const { if (indexes) { forEach([=](__unsafe_unretained auto o) { [o willChange:kind valuesAtIndexes:indexes forKey:key]; }); } else { forEach([=](__unsafe_unretained auto o) { [o willChangeValueForKey:key]; }); } } void RLMObservationInfo::didChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const { if (indexes) { forEach([=](__unsafe_unretained auto o) { [o didChange:kind valuesAtIndexes:indexes forKey:key]; }); } else { forEach([=](__unsafe_unretained auto o) { [o didChangeValueForKey:key]; }); } } void RLMObservationInfo::prepareForInvalidation() { REALM_ASSERT_DEBUG(objectSchema); REALM_ASSERT_DEBUG(!prev); for (auto info = this; info; info = info->next) info->invalidated = true; } void RLMObservationInfo::setRow(realm::Table &table, size_t newRow) { REALM_ASSERT_DEBUG(!row); REALM_ASSERT_DEBUG(objectSchema); row = table[newRow]; for (auto info : objectSchema->observedObjects) { if (info->row && info->row.get_index() == row.get_index()) { prev = info; next = info->next; if (next) next->prev = this; info->next = this; return; } } objectSchema->observedObjects.push_back(this); } void RLMObservationInfo::recordObserver(realm::Row& objectRow, RLMClassInfo *objectInfo, __unsafe_unretained RLMObjectSchema *const objectSchema, __unsafe_unretained NSString *const keyPath) { ++observerCount; if (row) { return; } // add ourselves to the list of observed objects if this is the first time // an observer is being added to a managed object if (objectRow) { this->objectSchema = objectInfo; setRow(*objectRow.get_table(), objectRow.get_index()); return; } // Arrays need a reference to their containing object to avoid having to // go through the awful proxy object from mutableArrayValueForKey. // For managed objects we do this when the object is added or created // (and have to to support notifications from modifying an object which // was never observed), but for Swift classes (both RealmSwift and // RLMObject) we can't do it then because we don't know what the parent // object is. NSUInteger sep = [keyPath rangeOfString:@"."].location; NSString *key = sep == NSNotFound ? keyPath : [keyPath substringToIndex:sep]; RLMProperty *prop = objectSchema[key]; if (prop && prop.array) { id value = valueForKey(key); RLMArray *array = [value isKindOfClass:[RLMListBase class]] ? [value _rlmArray] : value; array->_key = key; array->_parentObject = object; } else if (auto swiftIvar = prop.swiftIvar) { if (auto optional = RLMDynamicCast(object_getIvar(object, swiftIvar))) { RLMInitializeUnmanagedOptional(optional, object, prop); } } } void RLMObservationInfo::removeObserver() { --observerCount; } id RLMObservationInfo::valueForKey(NSString *key) { if (invalidated) { if ([key isEqualToString:RLMInvalidatedKey]) { return @YES; } return cachedObjects[key]; } if (key != lastKey) { lastKey = key; lastProp = objectSchema ? objectSchema->rlmObjectSchema[key] : nil; } static auto superValueForKey = reinterpret_cast([NSObject methodForSelector:@selector(valueForKey:)]); if (!lastProp) { // Not a managed property, so use NSObject's implementation of valueForKey: return RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key)); } auto getSuper = [&] { return row ? RLMDynamicGet(object, lastProp) : RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key)); }; // We need to return the same object each time for observing over keypaths // to work, so we store a cache of them here. We can't just cache them on // the object as that leads to retain cycles. if (lastProp.array) { RLMArray *value = cachedObjects[key]; if (!value) { value = getSuper(); if (!cachedObjects) { cachedObjects = [NSMutableDictionary new]; } cachedObjects[key] = value; } return value; } if (lastProp.type == RLMPropertyTypeObject) { size_t col = row.get_column_index(lastProp.name.UTF8String); if (row.is_null_link(col)) { [cachedObjects removeObjectForKey:key]; return nil; } RLMObjectBase *value = cachedObjects[key]; if (value && value->_row.get_index() == row.get_link(col)) { return value; } value = getSuper(); if (!cachedObjects) { cachedObjects = [NSMutableDictionary new]; } cachedObjects[key] = value; return value; } return getSuper(); } RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info, size_t row, RLMClassInfo& objectSchema) { if (info) { return info; } for (RLMObservationInfo *info : objectSchema.observedObjects) { if (info->isForRow(row)) { return info; } } return nullptr; } void RLMClearTable(RLMClassInfo &objectSchema) { for (auto info : objectSchema.observedObjects) { info->willChange(RLMInvalidatedKey); } RLMTrackDeletions(objectSchema.realm, ^{ Results(objectSchema.realm->_realm, *objectSchema.table()).clear(); for (auto info : objectSchema.observedObjects) { info->prepareForInvalidation(); } }); for (auto info : reverse(objectSchema.observedObjects)) { info->didChange(RLMInvalidatedKey); } objectSchema.observedObjects.clear(); } void RLMTrackDeletions(__unsafe_unretained RLMRealm *const realm, dispatch_block_t block) { std::vector *> observers; // Build up an array of observation info arrays which is indexed by table // index (the object schemata may be in an entirely different order) for (auto& info : realm->_info) { if (info.second.observedObjects.empty()) { continue; } size_t ndx = info.second.table()->get_index_in_group(); if (ndx >= observers.size()) { observers.resize(std::max(observers.size() * 2, ndx + 1)); } observers[ndx] = &info.second.observedObjects; } // No need for change tracking if no objects are observed if (observers.empty()) { block(); return; } struct change { RLMObservationInfo *info; __unsafe_unretained NSString *property; NSMutableIndexSet *indexes; }; std::vector changes; std::vector invalidated; // This callback is called by core with a list of row deletions and // resulting link nullifications immediately before things are deleted and nullified realm.group.set_cascade_notification_handler([&](realm::Group::CascadeNotification const& cs) { for (auto const& link : cs.links) { size_t table_ndx = link.origin_table->get_index_in_group(); if (table_ndx >= observers.size() || !observers[table_ndx]) { // The modified table has no observers continue; } for (auto observer : *observers[table_ndx]) { if (!observer->isForRow(link.origin_row_ndx)) { continue; } NSString *name = observer->columnName(link.origin_col_ndx); if (observer->getRow().get_table()->get_column_type(link.origin_col_ndx) != type_LinkList) { changes.push_back({observer, name}); continue; } auto c = find_if(begin(changes), end(changes), [&](auto const& c) { return c.info == observer && c.property == name; }); if (c == end(changes)) { changes.push_back({observer, name, [NSMutableIndexSet new]}); c = prev(end(changes)); } // We know what row index is being removed from the LinkView, // but what we actually want is the indexes in the LinkView that // are going away auto linkview = observer->getRow().get_linklist(link.origin_col_ndx); size_t start = 0, index; while ((index = linkview->find(link.old_target_row_ndx, start)) != realm::not_found) { [c->indexes addIndex:index]; start = index + 1; } } } for (auto const& row : cs.rows) { if (row.table_ndx >= observers.size() || !observers[row.table_ndx]) { // The modified table has no observers continue; } for (auto observer : *observers[row.table_ndx]) { if (observer->isForRow(row.row_ndx)) { invalidated.push_back(observer); break; } } } // The relative order of these loops is very important for (auto info : invalidated) { info->willChange(RLMInvalidatedKey); } for (auto const& change : changes) { change.info->willChange(change.property, NSKeyValueChangeRemoval, change.indexes); } for (auto info : invalidated) { info->prepareForInvalidation(); } }); try { block(); } catch (...) { realm.group.set_cascade_notification_handler(nullptr); throw; } for (auto const& change : reverse(changes)) { change.info->didChange(change.property, NSKeyValueChangeRemoval, change.indexes); } for (auto info : reverse(invalidated)) { info->didChange(RLMInvalidatedKey); } realm.group.set_cascade_notification_handler(nullptr); } namespace { template void forEach(realm::BindingContext::ObserverState const& state, Func&& func) { for (size_t i = 0, size = state.changes.size(); i < size; ++i) { if (state.changes[i].kind != realm::BindingContext::ColumnInfo::Kind::None) { func(i, state.changes[i], static_cast(state.info)); } } } } std::vector RLMGetObservedRows(RLMSchemaInfo const& schema) { std::vector observers; for (auto& table : schema) { for (auto info : table.second.observedObjects) { auto const& row = info->getRow(); if (!row.is_attached()) continue; observers.push_back({ row.get_table()->get_index_in_group(), row.get_index(), info}); } } sort(begin(observers), end(observers)); return observers; } static NSKeyValueChange convert(realm::BindingContext::ColumnInfo::Kind kind) { switch (kind) { case realm::BindingContext::ColumnInfo::Kind::None: case realm::BindingContext::ColumnInfo::Kind::SetAll: return NSKeyValueChangeSetting; case realm::BindingContext::ColumnInfo::Kind::Set: return NSKeyValueChangeReplacement; case realm::BindingContext::ColumnInfo::Kind::Insert: return NSKeyValueChangeInsertion; case realm::BindingContext::ColumnInfo::Kind::Remove: return NSKeyValueChangeRemoval; } } static NSIndexSet *convert(realm::IndexSet const& in, NSMutableIndexSet *out) { if (in.empty()) { return nil; } [out removeAllIndexes]; for (auto range : in) { [out addIndexesInRange:{range.first, range.second - range.first}]; } return out; } void RLMWillChange(std::vector const& observed, std::vector const& invalidated) { for (auto info : invalidated) { static_cast(info)->willChange(RLMInvalidatedKey); } if (!observed.empty()) { NSMutableIndexSet *indexes = [NSMutableIndexSet new]; for (auto const& o : observed) { forEach(o, [&](size_t, auto const& change, RLMObservationInfo *info) { info->willChange(info->columnName(change.initial_column_index), convert(change.kind), convert(change.indices, indexes)); }); } } for (auto info : invalidated) { static_cast(info)->prepareForInvalidation(); } } void RLMDidChange(std::vector const& observed, std::vector const& invalidated) { if (!observed.empty()) { // Loop in reverse order to avoid O(N^2) behavior in Foundation NSMutableIndexSet *indexes = [NSMutableIndexSet new]; for (auto const& o : reverse(observed)) { forEach(o, [&](size_t i, auto const& change, RLMObservationInfo *info) { info->didChange(info->columnName(i), convert(change.kind), convert(change.indices, indexes)); }); } } for (auto const& info : reverse(invalidated)) { static_cast(info)->didChange(RLMInvalidatedKey); } }