1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2015 Realm Inc.
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
17 ////////////////////////////////////////////////////////////////////////////
19 #import "RLMObservation.hpp"
21 #import "RLMAccessor.h"
22 #import "RLMArray_Private.hpp"
23 #import "RLMListBase.h"
24 #import "RLMObjectSchema_Private.hpp"
25 #import "RLMObject_Private.hpp"
26 #import "RLMProperty_Private.h"
27 #import "RLMRealm_Private.hpp"
29 #import <realm/group.hpp>
31 using namespace realm;
34 template<typename Iterator>
39 template<typename Iterator>
40 Iterator begin(IteratorPair<Iterator> const& p) {
43 template<typename Iterator>
44 Iterator end(IteratorPair<Iterator> const& p) {
48 template<typename Container>
49 auto reverse(Container const& c) {
50 return IteratorPair<typename Container::const_reverse_iterator>{c.rbegin(), c.rend()};
54 RLMObservationInfo::RLMObservationInfo(RLMClassInfo &objectSchema, std::size_t row, id object)
56 , objectSchema(&objectSchema)
58 setRow(*objectSchema.table(), row);
61 RLMObservationInfo::RLMObservationInfo(id object)
66 RLMObservationInfo::~RLMObservationInfo() {
68 // Not the head of the linked list, so just detach from the list
69 REALM_ASSERT_DEBUG(prev->next == this);
72 REALM_ASSERT_DEBUG(next->prev == this);
76 else if (objectSchema) {
77 // The head of the list, so remove self from the object schema's array
78 // of observation info, either replacing self with the next info or
79 // removing entirely if there is no next
80 auto end = objectSchema->observedObjects.end();
81 auto it = find(objectSchema->observedObjects.begin(), end, this);
88 iter_swap(it, std::prev(end));
89 objectSchema->observedObjects.pop_back();
93 // Otherwise the observed object was unmanaged, so nothing to do
96 // ensure that incorrect cleanup fails noisily
97 object = (__bridge id)(void *)-1;
98 prev = (RLMObservationInfo *)-1;
99 next = (RLMObservationInfo *)-1;
103 NSString *RLMObservationInfo::columnName(size_t col) const noexcept {
104 return objectSchema->propertyForTableColumn(col).name;
107 void RLMObservationInfo::willChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const {
109 forEach([=](__unsafe_unretained auto o) {
110 [o willChange:kind valuesAtIndexes:indexes forKey:key];
114 forEach([=](__unsafe_unretained auto o) {
115 [o willChangeValueForKey:key];
120 void RLMObservationInfo::didChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const {
122 forEach([=](__unsafe_unretained auto o) {
123 [o didChange:kind valuesAtIndexes:indexes forKey:key];
127 forEach([=](__unsafe_unretained auto o) {
128 [o didChangeValueForKey:key];
133 void RLMObservationInfo::prepareForInvalidation() {
134 REALM_ASSERT_DEBUG(objectSchema);
135 REALM_ASSERT_DEBUG(!prev);
136 for (auto info = this; info; info = info->next)
137 info->invalidated = true;
140 void RLMObservationInfo::setRow(realm::Table &table, size_t newRow) {
141 REALM_ASSERT_DEBUG(!row);
142 REALM_ASSERT_DEBUG(objectSchema);
144 for (auto info : objectSchema->observedObjects) {
145 if (info->row && info->row.get_index() == row.get_index()) {
154 objectSchema->observedObjects.push_back(this);
157 void RLMObservationInfo::recordObserver(realm::Row& objectRow, RLMClassInfo *objectInfo,
158 __unsafe_unretained RLMObjectSchema *const objectSchema,
159 __unsafe_unretained NSString *const keyPath) {
165 // add ourselves to the list of observed objects if this is the first time
166 // an observer is being added to a managed object
168 this->objectSchema = objectInfo;
169 setRow(*objectRow.get_table(), objectRow.get_index());
173 // Arrays need a reference to their containing object to avoid having to
174 // go through the awful proxy object from mutableArrayValueForKey.
175 // For managed objects we do this when the object is added or created
176 // (and have to to support notifications from modifying an object which
177 // was never observed), but for Swift classes (both RealmSwift and
178 // RLMObject) we can't do it then because we don't know what the parent
181 NSUInteger sep = [keyPath rangeOfString:@"."].location;
182 NSString *key = sep == NSNotFound ? keyPath : [keyPath substringToIndex:sep];
183 RLMProperty *prop = objectSchema[key];
184 if (prop && prop.array) {
185 id value = valueForKey(key);
186 RLMArray *array = [value isKindOfClass:[RLMListBase class]] ? [value _rlmArray] : value;
188 array->_parentObject = object;
190 else if (auto swiftIvar = prop.swiftIvar) {
191 if (auto optional = RLMDynamicCast<RLMOptionalBase>(object_getIvar(object, swiftIvar))) {
192 optional.property = prop;
193 optional.object = object;
198 void RLMObservationInfo::removeObserver() {
202 id RLMObservationInfo::valueForKey(NSString *key) {
204 if ([key isEqualToString:RLMInvalidatedKey]) {
207 return cachedObjects[key];
210 if (key != lastKey) {
212 lastProp = objectSchema ? objectSchema->rlmObjectSchema[key] : nil;
215 static auto superValueForKey = reinterpret_cast<id(*)(id, SEL, NSString *)>([NSObject methodForSelector:@selector(valueForKey:)]);
217 // Not a managed property, so use NSObject's implementation of valueForKey:
218 return RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key));
221 auto getSuper = [&] {
222 return row ? RLMDynamicGet(object, lastProp) : RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key));
225 // We need to return the same object each time for observing over keypaths
226 // to work, so we store a cache of them here. We can't just cache them on
227 // the object as that leads to retain cycles.
228 if (lastProp.array) {
229 RLMArray *value = cachedObjects[key];
232 if (!cachedObjects) {
233 cachedObjects = [NSMutableDictionary new];
235 cachedObjects[key] = value;
240 if (lastProp.type == RLMPropertyTypeObject) {
241 size_t col = row.get_column_index(lastProp.name.UTF8String);
242 if (row.is_null_link(col)) {
243 [cachedObjects removeObjectForKey:key];
247 RLMObjectBase *value = cachedObjects[key];
248 if (value && value->_row.get_index() == row.get_link(col)) {
252 if (!cachedObjects) {
253 cachedObjects = [NSMutableDictionary new];
255 cachedObjects[key] = value;
262 RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info, size_t row,
263 RLMClassInfo& objectSchema) {
268 for (RLMObservationInfo *info : objectSchema.observedObjects) {
269 if (info->isForRow(row)) {
277 void RLMClearTable(RLMClassInfo &objectSchema) {
278 for (auto info : objectSchema.observedObjects) {
279 info->willChange(RLMInvalidatedKey);
282 RLMTrackDeletions(objectSchema.realm, ^{
283 objectSchema.table()->clear();
285 for (auto info : objectSchema.observedObjects) {
286 info->prepareForInvalidation();
290 for (auto info : reverse(objectSchema.observedObjects)) {
291 info->didChange(RLMInvalidatedKey);
294 objectSchema.observedObjects.clear();
297 void RLMTrackDeletions(__unsafe_unretained RLMRealm *const realm, dispatch_block_t block) {
298 std::vector<std::vector<RLMObservationInfo *> *> observers;
300 // Build up an array of observation info arrays which is indexed by table
301 // index (the object schemata may be in an entirely different order)
302 for (auto& info : realm->_info) {
303 if (info.second.observedObjects.empty()) {
306 size_t ndx = info.second.table()->get_index_in_group();
307 if (ndx >= observers.size()) {
308 observers.resize(std::max(observers.size() * 2, ndx + 1));
310 observers[ndx] = &info.second.observedObjects;
313 // No need for change tracking if no objects are observed
314 if (observers.empty()) {
320 RLMObservationInfo *info;
321 __unsafe_unretained NSString *property;
322 NSMutableIndexSet *indexes;
325 std::vector<change> changes;
326 std::vector<RLMObservationInfo *> invalidated;
328 // This callback is called by core with a list of row deletions and
329 // resulting link nullifications immediately before things are deleted and nullified
330 realm.group.set_cascade_notification_handler([&](realm::Group::CascadeNotification const& cs) {
331 for (auto const& link : cs.links) {
332 size_t table_ndx = link.origin_table->get_index_in_group();
333 if (table_ndx >= observers.size() || !observers[table_ndx]) {
334 // The modified table has no observers
338 for (auto observer : *observers[table_ndx]) {
339 if (!observer->isForRow(link.origin_row_ndx)) {
343 NSString *name = observer->columnName(link.origin_col_ndx);
344 if (observer->getRow().get_table()->get_column_type(link.origin_col_ndx) != type_LinkList) {
345 changes.push_back({observer, name});
349 auto c = find_if(begin(changes), end(changes), [&](auto const& c) {
350 return c.info == observer && c.property == name;
352 if (c == end(changes)) {
353 changes.push_back({observer, name, [NSMutableIndexSet new]});
354 c = prev(end(changes));
357 // We know what row index is being removed from the LinkView,
358 // but what we actually want is the indexes in the LinkView that
360 auto linkview = observer->getRow().get_linklist(link.origin_col_ndx);
361 size_t start = 0, index;
362 while ((index = linkview->find(link.old_target_row_ndx, start)) != realm::not_found) {
363 [c->indexes addIndex:index];
369 for (auto const& row : cs.rows) {
370 if (row.table_ndx >= observers.size() || !observers[row.table_ndx]) {
371 // The modified table has no observers
375 for (auto observer : *observers[row.table_ndx]) {
376 if (observer->isForRow(row.row_ndx)) {
377 invalidated.push_back(observer);
383 // The relative order of these loops is very important
384 for (auto info : invalidated) {
385 info->willChange(RLMInvalidatedKey);
387 for (auto const& change : changes) {
388 change.info->willChange(change.property, NSKeyValueChangeRemoval, change.indexes);
390 for (auto info : invalidated) {
391 info->prepareForInvalidation();
399 realm.group.set_cascade_notification_handler(nullptr);
403 for (auto const& change : reverse(changes)) {
404 change.info->didChange(change.property, NSKeyValueChangeRemoval, change.indexes);
406 for (auto info : reverse(invalidated)) {
407 info->didChange(RLMInvalidatedKey);
410 realm.group.set_cascade_notification_handler(nullptr);
414 template<typename Func>
415 void forEach(realm::BindingContext::ObserverState const& state, Func&& func) {
416 for (size_t i = 0, size = state.changes.size(); i < size; ++i) {
417 if (state.changes[i].kind != realm::BindingContext::ColumnInfo::Kind::None) {
418 func(i, state.changes[i], static_cast<RLMObservationInfo *>(state.info));
424 std::vector<realm::BindingContext::ObserverState> RLMGetObservedRows(RLMSchemaInfo const& schema) {
425 std::vector<realm::BindingContext::ObserverState> observers;
426 for (auto& table : schema) {
427 for (auto info : table.second.observedObjects) {
428 auto const& row = info->getRow();
429 if (!row.is_attached())
431 observers.push_back({
432 row.get_table()->get_index_in_group(),
437 sort(begin(observers), end(observers));
441 static NSKeyValueChange convert(realm::BindingContext::ColumnInfo::Kind kind) {
443 case realm::BindingContext::ColumnInfo::Kind::None:
444 case realm::BindingContext::ColumnInfo::Kind::SetAll:
445 return NSKeyValueChangeSetting;
446 case realm::BindingContext::ColumnInfo::Kind::Set:
447 return NSKeyValueChangeReplacement;
448 case realm::BindingContext::ColumnInfo::Kind::Insert:
449 return NSKeyValueChangeInsertion;
450 case realm::BindingContext::ColumnInfo::Kind::Remove:
451 return NSKeyValueChangeRemoval;
455 static NSIndexSet *convert(realm::IndexSet const& in, NSMutableIndexSet *out) {
460 [out removeAllIndexes];
461 for (auto range : in) {
462 [out addIndexesInRange:{range.first, range.second - range.first}];
467 void RLMWillChange(std::vector<realm::BindingContext::ObserverState> const& observed,
468 std::vector<void *> const& invalidated) {
469 for (auto info : invalidated) {
470 static_cast<RLMObservationInfo *>(info)->willChange(RLMInvalidatedKey);
472 if (!observed.empty()) {
473 NSMutableIndexSet *indexes = [NSMutableIndexSet new];
474 for (auto const& o : observed) {
475 forEach(o, [&](size_t, auto const& change, RLMObservationInfo *info) {
476 info->willChange(info->columnName(change.initial_column_index),
477 convert(change.kind), convert(change.indices, indexes));
481 for (auto info : invalidated) {
482 static_cast<RLMObservationInfo *>(info)->prepareForInvalidation();
486 void RLMDidChange(std::vector<realm::BindingContext::ObserverState> const& observed,
487 std::vector<void *> const& invalidated) {
488 if (!observed.empty()) {
489 // Loop in reverse order to avoid O(N^2) behavior in Foundation
490 NSMutableIndexSet *indexes = [NSMutableIndexSet new];
491 for (auto const& o : reverse(observed)) {
492 forEach(o, [&](size_t i, auto const& change, RLMObservationInfo *info) {
493 info->didChange(info->columnName(i), convert(change.kind), convert(change.indices, indexes));
497 for (auto const& info : reverse(invalidated)) {
498 static_cast<RLMObservationInfo *>(info)->didChange(RLMInvalidatedKey);