1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2016 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 "RLMCollection_Private.hpp"
21 #import "RLMAccessor.hpp"
22 #import "RLMArray_Private.hpp"
23 #import "RLMListBase.h"
24 #import "RLMObjectSchema_Private.hpp"
25 #import "RLMObjectStore.h"
26 #import "RLMObject_Private.hpp"
27 #import "RLMProperty_Private.h"
29 #import "collection_notifications.hpp"
33 static const int RLMEnumerationBufferSize = 16;
35 @implementation RLMFastEnumerator {
36 // The buffer supplied by fast enumeration does not retain the objects given
37 // to it, but because we create objects on-demand and don't want them
38 // autoreleased (a table can have more rows than the device has memory for
39 // accessor objects) we need a thing to retain them.
40 id _strongBuffer[RLMEnumerationBufferSize];
45 // A pointer to either _snapshot or a Results from the source collection,
46 // to avoid having to copy the Results when not in a write transaction
47 realm::Results *_results;
48 realm::Results _snapshot;
50 // A strong reference to the collection being enumerated to ensure it stays
51 // alive when we're holding a pointer to a member in it
55 - (instancetype)initWithList:(realm::List&)list
56 collection:(id)collection
57 realm:(RLMRealm *)realm
58 classInfo:(RLMClassInfo&)info
62 if (realm.inWriteTransaction) {
63 _snapshot = list.snapshot();
66 _snapshot = list.as_results();
67 _collection = collection;
68 [realm registerEnumerator:self];
70 _results = &_snapshot;
77 - (instancetype)initWithResults:(realm::Results&)results
78 collection:(id)collection
79 realm:(RLMRealm *)realm
80 classInfo:(RLMClassInfo&)info
84 if (realm.inWriteTransaction) {
85 _snapshot = results.snapshot();
86 _results = &_snapshot;
90 _collection = collection;
91 [realm registerEnumerator:self];
101 [_realm unregisterEnumerator:self];
106 _snapshot = _results->snapshot();
107 _results = &_snapshot;
111 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
112 count:(NSUInteger)len {
113 [_realm verifyThread];
114 if (!_results->is_valid()) {
115 @throw RLMException(@"Collection is no longer valid");
117 // The fast enumeration buffer size is currently a hardcoded number in the
118 // compiler so this can't actually happen, but just in case it changes in
120 if (len > RLMEnumerationBufferSize) {
121 len = RLMEnumerationBufferSize;
124 NSUInteger batchCount = 0, count = state->extra[1];
127 RLMAccessorContext ctx(_realm, *_info);
128 for (NSUInteger index = state->state; index < count && batchCount < len; ++index) {
129 _strongBuffer[batchCount] = _results->get(ctx, index);
134 for (NSUInteger i = batchCount; i < len; ++i) {
135 _strongBuffer[i] = nil;
138 if (batchCount == 0) {
139 // Release our data if we're done, as we're autoreleased and so may
140 // stick around for a while
143 [_realm unregisterEnumerator:self];
148 state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer;
149 state->state += batchCount;
150 state->mutationsPtr = state->extra+1;
156 NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, id<RLMFastEnumerable> collection) {
157 __autoreleasing RLMFastEnumerator *enumerator;
158 if (state->state == 0) {
159 enumerator = collection.fastEnumerator;
160 state->extra[0] = (long)enumerator;
161 state->extra[1] = collection.count;
164 enumerator = (__bridge id)(void *)state->extra[0];
167 return [enumerator countByEnumeratingWithState:state count:len];
170 template<typename Collection>
171 NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key,
172 RLMRealm *realm, RLMClassInfo& info) {
173 size_t count = collection.size();
178 NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
179 if ([key isEqualToString:@"self"]) {
180 RLMAccessorContext context(realm, info);
181 for (size_t i = 0; i < count; ++i) {
182 [array addObject:collection.get(context, i) ?: NSNull.null];
187 if (collection.get_type() != realm::PropertyType::Object) {
188 RLMAccessorContext context(realm, info);
189 for (size_t i = 0; i < count; ++i) {
190 [array addObject:[collection.get(context, i) valueForKey:key] ?: NSNull.null];
195 RLMObject *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, realm, &info);
197 // List properties need to be handled specially since we need to create a
198 // new List each time
199 if (info.rlmObjectSchema.isSwiftClass) {
200 auto prop = info.rlmObjectSchema[key];
201 if (prop && prop.array && prop.swiftIvar) {
202 // Grab the actual class for the generic List from an instance of it
203 // so that we can make instances of the List without creating a new
204 // object accessor each time
205 Class cls = [object_getIvar(accessor, prop.swiftIvar) class];
206 RLMAccessorContext context(realm, info);
207 for (size_t i = 0; i < count; ++i) {
208 RLMListBase *list = [[cls alloc] init];
209 list._rlmArray = [[RLMManagedArray alloc] initWithList:realm::List(realm->_realm, *info.table(),
210 info.tableColumn(prop),
211 collection.get(i).get_index())
212 realm:realm parentInfo:&info
214 [array addObject:list];
220 for (size_t i = 0; i < count; i++) {
221 accessor->_row = collection.get(i);
222 RLMInitializeSwiftAccessorGenerics(accessor);
223 [array addObject:[accessor valueForKey:key] ?: NSNull.null];
228 template NSArray *RLMCollectionValueForKey(realm::Results&, NSString *, RLMRealm *, RLMClassInfo&);
229 template NSArray *RLMCollectionValueForKey(realm::List&, NSString *, RLMRealm *, RLMClassInfo&);
231 void RLMCollectionSetValueForKey(id<RLMFastEnumerable> collection, NSString *key, id value) {
232 realm::TableView tv = [collection tableView];
233 if (tv.size() == 0) {
237 RLMRealm *realm = collection.realm;
238 RLMClassInfo *info = collection.objectInfo;
239 RLMObject *accessor = RLMCreateManagedAccessor(info->rlmObjectSchema.accessorClass, realm, info);
240 for (size_t i = 0; i < tv.size(); i++) {
241 accessor->_row = tv[i];
242 RLMInitializeSwiftAccessorGenerics(accessor);
243 [accessor setValue:value forKey:key];
247 NSString *RLMDescriptionWithMaxDepth(NSString *name,
248 id<RLMCollection> collection,
251 return @"<Maximum depth exceeded>";
254 const NSUInteger maxObjects = 100;
255 auto str = [NSMutableString stringWithFormat:@"%@<%@> <%p> (\n", name,
256 [collection objectClassName] ?: RLMTypeToString([collection type]),
258 size_t index = 0, skipped = 0;
259 for (id obj in collection) {
261 if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
262 sub = [obj descriptionWithMaxDepth:depth - 1];
265 sub = [obj description];
268 // Indent child objects
269 NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n"
271 [str appendFormat:@"\t[%zu] %@,\n", index++, objDescription];
272 if (index >= maxObjects) {
273 skipped = collection.count - maxObjects;
278 // Remove last comma and newline characters
279 if (collection.count > 0) {
280 [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)];
283 [str appendFormat:@"\n\t... %zu objects skipped.", skipped];
285 [str appendFormat:@"\n)"];
289 std::vector<std::pair<std::string, bool>> RLMSortDescriptorsToKeypathArray(NSArray<RLMSortDescriptor *> *properties) {
290 std::vector<std::pair<std::string, bool>> keypaths;
291 keypaths.reserve(properties.count);
292 for (RLMSortDescriptor *desc in properties) {
293 if ([desc.keyPath rangeOfString:@"@"].location != NSNotFound) {
294 @throw RLMException(@"Cannot sort on key path '%@': KVC collection operators are not supported.", desc.keyPath);
296 keypaths.push_back({desc.keyPath.UTF8String, desc.ascending});
301 @implementation RLMCancellationToken {
302 realm::NotificationToken _token;
303 __unsafe_unretained RLMRealm *_realm;
305 - (instancetype)initWithToken:(realm::NotificationToken)token realm:(RLMRealm *)realm {
308 _token = std::move(token);
314 - (RLMRealm *)realm {
318 - (void)suppressNextNotification {
319 _token.suppress_next();
328 @implementation RLMCollectionChange {
329 realm::CollectionChangeSet _indices;
332 - (instancetype)initWithChanges:(realm::CollectionChangeSet)indices {
335 _indices = std::move(indices);
340 static NSArray *toArray(realm::IndexSet const& set) {
341 NSMutableArray *ret = [NSMutableArray new];
342 for (auto index : set.as_indexes()) {
343 [ret addObject:@(index)];
348 - (NSArray *)insertions {
349 return toArray(_indices.insertions);
352 - (NSArray *)deletions {
353 return toArray(_indices.deletions);
356 - (NSArray *)modifications {
357 return toArray(_indices.modifications);
360 static NSArray *toIndexPathArray(realm::IndexSet const& set, NSUInteger section) {
361 NSMutableArray *ret = [NSMutableArray new];
362 NSUInteger path[2] = {section, 0};
363 for (auto index : set.as_indexes()) {
365 [ret addObject:[NSIndexPath indexPathWithIndexes:path length:2]];
370 - (NSArray<NSIndexPath *> *)deletionsInSection:(NSUInteger)section {
371 return toIndexPathArray(_indices.deletions, section);
374 - (NSArray<NSIndexPath *> *)insertionsInSection:(NSUInteger)section {
375 return toIndexPathArray(_indices.insertions, section);
379 - (NSArray<NSIndexPath *> *)modificationsInSection:(NSUInteger)section {
380 return toIndexPathArray(_indices.modifications, section);
385 template<typename Collection>
386 RLMNotificationToken *RLMAddNotificationBlock(id objcCollection,
387 Collection& collection,
388 void (^block)(id, RLMCollectionChange *, NSError *),
389 bool suppressInitialChange) {
390 auto skip = suppressInitialChange ? std::make_shared<bool>(true) : nullptr;
391 auto cb = [=, &collection](realm::CollectionChangeSet const& changes,
392 std::exception_ptr err) {
395 rethrow_exception(err);
398 NSError *error = nil;
399 RLMRealmTranslateException(&error);
400 block(nil, nil, error);
407 block(objcCollection, nil, nil);
409 else if (changes.empty()) {
410 block(objcCollection, nil, nil);
413 block(objcCollection, [[RLMCollectionChange alloc] initWithChanges:changes], nil);
417 return [[RLMCancellationToken alloc] initWithToken:collection.add_notification_callback(cb)
418 realm:(RLMRealm *)[objcCollection realm]];
421 // Explicitly instantiate the templated function for the two types we'll use it on
422 template RLMNotificationToken *RLMAddNotificationBlock<realm::List>(id, realm::List&, void (^)(id, RLMCollectionChange *, NSError *), bool);
423 template RLMNotificationToken *RLMAddNotificationBlock<realm::Results>(id, realm::Results&, void (^)(id, RLMCollectionChange *, NSError *), bool);