added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMCollection.mm
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2016 Realm Inc.
4 //
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
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
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.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #import "RLMCollection_Private.hpp"
20
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"
28
29 #import "collection_notifications.hpp"
30 #import "list.hpp"
31 #import "results.hpp"
32
33 static const int RLMEnumerationBufferSize = 16;
34
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];
41
42     RLMRealm *_realm;
43     RLMClassInfo *_info;
44
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;
49
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
52     id _collection;
53 }
54
55 - (instancetype)initWithList:(realm::List&)list
56                   collection:(id)collection
57                        realm:(RLMRealm *)realm
58                    classInfo:(RLMClassInfo&)info
59 {
60     self = [super init];
61     if (self) {
62         if (realm.inWriteTransaction) {
63             _snapshot = list.snapshot();
64         }
65         else {
66             _snapshot = list.as_results();
67             _collection = collection;
68             [realm registerEnumerator:self];
69         }
70         _results = &_snapshot;
71         _realm = realm;
72         _info = &info;
73     }
74     return self;
75 }
76
77 - (instancetype)initWithResults:(realm::Results&)results
78                      collection:(id)collection
79                           realm:(RLMRealm *)realm
80                       classInfo:(RLMClassInfo&)info
81 {
82     self = [super init];
83     if (self) {
84         if (realm.inWriteTransaction) {
85             _snapshot = results.snapshot();
86             _results = &_snapshot;
87         }
88         else {
89             _results = &results;
90             _collection = collection;
91             [realm registerEnumerator:self];
92         }
93         _realm = realm;
94         _info = &info;
95     }
96     return self;
97 }
98
99 - (void)dealloc {
100     if (_collection) {
101         [_realm unregisterEnumerator:self];
102     }
103 }
104
105 - (void)detach {
106     _snapshot = _results->snapshot();
107     _results = &_snapshot;
108     _collection = nil;
109 }
110
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");
116     }
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
119     // the future...
120     if (len > RLMEnumerationBufferSize) {
121         len = RLMEnumerationBufferSize;
122     }
123
124     NSUInteger batchCount = 0, count = state->extra[1];
125
126     @autoreleasepool {
127         RLMAccessorContext ctx(_realm, *_info);
128         for (NSUInteger index = state->state; index < count && batchCount < len; ++index) {
129             _strongBuffer[batchCount] = _results->get(ctx, index);
130             batchCount++;
131         }
132     }
133
134     for (NSUInteger i = batchCount; i < len; ++i) {
135         _strongBuffer[i] = nil;
136     }
137
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
141         if (_collection) {
142             _collection = nil;
143             [_realm unregisterEnumerator:self];
144         }
145         _snapshot = {};
146     }
147
148     state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer;
149     state->state += batchCount;
150     state->mutationsPtr = state->extra+1;
151
152     return batchCount;
153 }
154 @end
155
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;
162     }
163     else {
164         enumerator = (__bridge id)(void *)state->extra[0];
165     }
166
167     return [enumerator countByEnumeratingWithState:state count:len];
168 }
169
170 template<typename Collection>
171 NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key,
172                                   RLMRealm *realm, RLMClassInfo& info) {
173     size_t count = collection.size();
174     if (count == 0) {
175         return @[];
176     }
177
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];
183         }
184         return array;
185     }
186
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];
191         }
192         return array;
193     }
194
195     RLMObject *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, realm, &info);
196
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
213                                                               property:prop];
214                 [array addObject:list];
215             }
216             return array;
217         }
218     }
219
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];
224     }
225     return array;
226 }
227
228 template NSArray *RLMCollectionValueForKey(realm::Results&, NSString *, RLMRealm *, RLMClassInfo&);
229 template NSArray *RLMCollectionValueForKey(realm::List&, NSString *, RLMRealm *, RLMClassInfo&);
230
231 void RLMCollectionSetValueForKey(id<RLMFastEnumerable> collection, NSString *key, id value) {
232     realm::TableView tv = [collection tableView];
233     if (tv.size() == 0) {
234         return;
235     }
236
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];
244     }
245 }
246
247 NSString *RLMDescriptionWithMaxDepth(NSString *name,
248                                      id<RLMCollection> collection,
249                                      NSUInteger depth) {
250     if (depth == 0) {
251         return @"<Maximum depth exceeded>";
252     }
253
254     const NSUInteger maxObjects = 100;
255     auto str = [NSMutableString stringWithFormat:@"%@<%@> <%p> (\n", name,
256                 [collection objectClassName] ?: RLMTypeToString([collection type]),
257                 (void *)collection];
258     size_t index = 0, skipped = 0;
259     for (id obj in collection) {
260         NSString *sub;
261         if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
262             sub = [obj descriptionWithMaxDepth:depth - 1];
263         }
264         else {
265             sub = [obj description];
266         }
267
268         // Indent child objects
269         NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n"
270                                                                   withString:@"\n\t"];
271         [str appendFormat:@"\t[%zu] %@,\n", index++, objDescription];
272         if (index >= maxObjects) {
273             skipped = collection.count - maxObjects;
274             break;
275         }
276     }
277
278     // Remove last comma and newline characters
279     if (collection.count > 0) {
280         [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)];
281     }
282     if (skipped) {
283         [str appendFormat:@"\n\t... %zu objects skipped.", skipped];
284     }
285     [str appendFormat:@"\n)"];
286     return str;
287 }
288
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);
295         }
296         keypaths.push_back({desc.keyPath.UTF8String, desc.ascending});
297     }
298     return keypaths;
299 }
300
301 @implementation RLMCancellationToken {
302     realm::NotificationToken _token;
303     __unsafe_unretained RLMRealm *_realm;
304 }
305 - (instancetype)initWithToken:(realm::NotificationToken)token realm:(RLMRealm *)realm {
306     self = [super init];
307     if (self) {
308         _token = std::move(token);
309         _realm = realm;
310     }
311     return self;
312 }
313
314 - (RLMRealm *)realm {
315     return _realm;
316 }
317
318 - (void)suppressNextNotification {
319     _token.suppress_next();
320 }
321
322 - (void)invalidate {
323     _token = {};
324 }
325
326 @end
327
328 @implementation RLMCollectionChange {
329     realm::CollectionChangeSet _indices;
330 }
331
332 - (instancetype)initWithChanges:(realm::CollectionChangeSet)indices {
333     self = [super init];
334     if (self) {
335         _indices = std::move(indices);
336     }
337     return self;
338 }
339
340 static NSArray *toArray(realm::IndexSet const& set) {
341     NSMutableArray *ret = [NSMutableArray new];
342     for (auto index : set.as_indexes()) {
343         [ret addObject:@(index)];
344     }
345     return ret;
346 }
347
348 - (NSArray *)insertions {
349     return toArray(_indices.insertions);
350 }
351
352 - (NSArray *)deletions {
353     return toArray(_indices.deletions);
354 }
355
356 - (NSArray *)modifications {
357     return toArray(_indices.modifications);
358 }
359
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()) {
364         path[1] = index;
365         [ret addObject:[NSIndexPath indexPathWithIndexes:path length:2]];
366     }
367     return ret;
368 }
369
370 - (NSArray<NSIndexPath *> *)deletionsInSection:(NSUInteger)section {
371     return toIndexPathArray(_indices.deletions, section);
372 }
373
374 - (NSArray<NSIndexPath *> *)insertionsInSection:(NSUInteger)section {
375     return toIndexPathArray(_indices.insertions, section);
376
377 }
378
379 - (NSArray<NSIndexPath *> *)modificationsInSection:(NSUInteger)section {
380     return toIndexPathArray(_indices.modifications, section);
381
382 }
383 @end
384
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) {
393         if (err) {
394             try {
395                 rethrow_exception(err);
396             }
397             catch (...) {
398                 NSError *error = nil;
399                 RLMRealmTranslateException(&error);
400                 block(nil, nil, error);
401                 return;
402             }
403         }
404
405         if (skip && *skip) {
406             *skip = false;
407             block(objcCollection, nil, nil);
408         }
409         else if (changes.empty()) {
410             block(objcCollection, nil, nil);
411         }
412         else {
413             block(objcCollection, [[RLMCollectionChange alloc] initWithChanges:changes], nil);
414         }
415     };
416
417     return [[RLMCancellationToken alloc] initWithToken:collection.add_notification_callback(cb)
418                                                  realm:(RLMRealm *)[objcCollection realm]];
419 }
420
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);