An amazing project that generates micro reports from tournament results
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

////////////////////////////////////////////////////////////////////////////
//
// 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);
}
}