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.
499 lines
17 KiB
499 lines
17 KiB
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 <realm/group.hpp>
|
|
|
|
using namespace realm;
|
|
|
|
namespace {
|
|
template<typename Iterator>
|
|
struct IteratorPair {
|
|
Iterator first;
|
|
Iterator second;
|
|
};
|
|
template<typename Iterator>
|
|
Iterator begin(IteratorPair<Iterator> const& p) {
|
|
return p.first;
|
|
}
|
|
template<typename Iterator>
|
|
Iterator end(IteratorPair<Iterator> const& p) {
|
|
return p.second;
|
|
}
|
|
|
|
template<typename Container>
|
|
auto reverse(Container const& c) {
|
|
return IteratorPair<typename Container::const_reverse_iterator>{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<RLMOptionalBase>(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<id(*)(id, SEL, NSString *)>([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<std::vector<RLMObservationInfo *> *> 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<change> changes;
|
|
std::vector<RLMObservationInfo *> 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<typename Func>
|
|
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<RLMObservationInfo *>(state.info));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<realm::BindingContext::ObserverState> RLMGetObservedRows(RLMSchemaInfo const& schema) {
|
|
std::vector<realm::BindingContext::ObserverState> 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<realm::BindingContext::ObserverState> const& observed,
|
|
std::vector<void *> const& invalidated) {
|
|
for (auto info : invalidated) {
|
|
static_cast<RLMObservationInfo *>(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<RLMObservationInfo *>(info)->prepareForInvalidation();
|
|
}
|
|
}
|
|
|
|
void RLMDidChange(std::vector<realm::BindingContext::ObserverState> const& observed,
|
|
std::vector<void *> 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<RLMObservationInfo *>(info)->didChange(RLMInvalidatedKey);
|
|
}
|
|
}
|
|
|