RLMCollection.mm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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. #import "RLMCollection_Private.hpp"
  19. #import "RLMAccessor.hpp"
  20. #import "RLMArray_Private.hpp"
  21. #import "RLMListBase.h"
  22. #import "RLMObjectSchema_Private.hpp"
  23. #import "RLMObjectStore.h"
  24. #import "RLMObject_Private.hpp"
  25. #import "RLMProperty_Private.h"
  26. #import "collection_notifications.hpp"
  27. #import "list.hpp"
  28. #import "results.hpp"
  29. static const int RLMEnumerationBufferSize = 16;
  30. @implementation RLMFastEnumerator {
  31. // The buffer supplied by fast enumeration does not retain the objects given
  32. // to it, but because we create objects on-demand and don't want them
  33. // autoreleased (a table can have more rows than the device has memory for
  34. // accessor objects) we need a thing to retain them.
  35. id _strongBuffer[RLMEnumerationBufferSize];
  36. RLMRealm *_realm;
  37. RLMClassInfo *_info;
  38. // A pointer to either _snapshot or a Results from the source collection,
  39. // to avoid having to copy the Results when not in a write transaction
  40. realm::Results *_results;
  41. realm::Results _snapshot;
  42. // A strong reference to the collection being enumerated to ensure it stays
  43. // alive when we're holding a pointer to a member in it
  44. id _collection;
  45. }
  46. - (instancetype)initWithList:(realm::List&)list
  47. collection:(id)collection
  48. realm:(RLMRealm *)realm
  49. classInfo:(RLMClassInfo&)info
  50. {
  51. self = [super init];
  52. if (self) {
  53. if (realm.inWriteTransaction) {
  54. _snapshot = list.snapshot();
  55. }
  56. else {
  57. _snapshot = list.as_results();
  58. _collection = collection;
  59. [realm registerEnumerator:self];
  60. }
  61. _results = &_snapshot;
  62. _realm = realm;
  63. _info = &info;
  64. }
  65. return self;
  66. }
  67. - (instancetype)initWithResults:(realm::Results&)results
  68. collection:(id)collection
  69. realm:(RLMRealm *)realm
  70. classInfo:(RLMClassInfo&)info
  71. {
  72. self = [super init];
  73. if (self) {
  74. if (realm.inWriteTransaction) {
  75. _snapshot = results.snapshot();
  76. _results = &_snapshot;
  77. }
  78. else {
  79. _results = &results;
  80. _collection = collection;
  81. [realm registerEnumerator:self];
  82. }
  83. _realm = realm;
  84. _info = &info;
  85. }
  86. return self;
  87. }
  88. - (void)dealloc {
  89. if (_collection) {
  90. [_realm unregisterEnumerator:self];
  91. }
  92. }
  93. - (void)detach {
  94. _snapshot = _results->snapshot();
  95. _results = &_snapshot;
  96. _collection = nil;
  97. }
  98. - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
  99. count:(NSUInteger)len {
  100. [_realm verifyThread];
  101. if (!_results->is_valid()) {
  102. @throw RLMException(@"Collection is no longer valid");
  103. }
  104. // The fast enumeration buffer size is currently a hardcoded number in the
  105. // compiler so this can't actually happen, but just in case it changes in
  106. // the future...
  107. if (len > RLMEnumerationBufferSize) {
  108. len = RLMEnumerationBufferSize;
  109. }
  110. NSUInteger batchCount = 0, count = state->extra[1];
  111. @autoreleasepool {
  112. RLMAccessorContext ctx(_realm, *_info);
  113. for (NSUInteger index = state->state; index < count && batchCount < len; ++index) {
  114. _strongBuffer[batchCount] = _results->get(ctx, index);
  115. batchCount++;
  116. }
  117. }
  118. for (NSUInteger i = batchCount; i < len; ++i) {
  119. _strongBuffer[i] = nil;
  120. }
  121. if (batchCount == 0) {
  122. // Release our data if we're done, as we're autoreleased and so may
  123. // stick around for a while
  124. if (_collection) {
  125. _collection = nil;
  126. [_realm unregisterEnumerator:self];
  127. }
  128. _snapshot = {};
  129. }
  130. state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer;
  131. state->state += batchCount;
  132. state->mutationsPtr = state->extra+1;
  133. return batchCount;
  134. }
  135. @end
  136. NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, id<RLMFastEnumerable> collection) {
  137. __autoreleasing RLMFastEnumerator *enumerator;
  138. if (state->state == 0) {
  139. enumerator = collection.fastEnumerator;
  140. state->extra[0] = (long)enumerator;
  141. state->extra[1] = collection.count;
  142. }
  143. else {
  144. enumerator = (__bridge id)(void *)state->extra[0];
  145. }
  146. return [enumerator countByEnumeratingWithState:state count:len];
  147. }
  148. template<typename Collection>
  149. NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key,
  150. RLMRealm *realm, RLMClassInfo& info) {
  151. size_t count = collection.size();
  152. if (count == 0) {
  153. return @[];
  154. }
  155. NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
  156. if ([key isEqualToString:@"self"]) {
  157. RLMAccessorContext context(realm, info);
  158. for (size_t i = 0; i < count; ++i) {
  159. [array addObject:collection.get(context, i) ?: NSNull.null];
  160. }
  161. return array;
  162. }
  163. if (collection.get_type() != realm::PropertyType::Object) {
  164. RLMAccessorContext context(realm, info);
  165. for (size_t i = 0; i < count; ++i) {
  166. [array addObject:[collection.get(context, i) valueForKey:key] ?: NSNull.null];
  167. }
  168. return array;
  169. }
  170. RLMObject *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, realm, &info);
  171. // List properties need to be handled specially since we need to create a
  172. // new List each time
  173. if (info.rlmObjectSchema.isSwiftClass) {
  174. auto prop = info.rlmObjectSchema[key];
  175. if (prop && prop.array && prop.swiftIvar) {
  176. // Grab the actual class for the generic List from an instance of it
  177. // so that we can make instances of the List without creating a new
  178. // object accessor each time
  179. Class cls = [object_getIvar(accessor, prop.swiftIvar) class];
  180. RLMAccessorContext context(realm, info);
  181. for (size_t i = 0; i < count; ++i) {
  182. RLMListBase *list = [[cls alloc] init];
  183. list._rlmArray = [[RLMManagedArray alloc] initWithList:realm::List(realm->_realm, *info.table(),
  184. info.tableColumn(prop),
  185. collection.get(i).get_index())
  186. realm:realm parentInfo:&info
  187. property:prop];
  188. [array addObject:list];
  189. }
  190. return array;
  191. }
  192. }
  193. for (size_t i = 0; i < count; i++) {
  194. accessor->_row = collection.get(i);
  195. RLMInitializeSwiftAccessorGenerics(accessor);
  196. [array addObject:[accessor valueForKey:key] ?: NSNull.null];
  197. }
  198. return array;
  199. }
  200. template NSArray *RLMCollectionValueForKey(realm::Results&, NSString *, RLMRealm *, RLMClassInfo&);
  201. template NSArray *RLMCollectionValueForKey(realm::List&, NSString *, RLMRealm *, RLMClassInfo&);
  202. void RLMCollectionSetValueForKey(id<RLMFastEnumerable> collection, NSString *key, id value) {
  203. realm::TableView tv = [collection tableView];
  204. if (tv.size() == 0) {
  205. return;
  206. }
  207. RLMRealm *realm = collection.realm;
  208. RLMClassInfo *info = collection.objectInfo;
  209. RLMObject *accessor = RLMCreateManagedAccessor(info->rlmObjectSchema.accessorClass, realm, info);
  210. for (size_t i = 0; i < tv.size(); i++) {
  211. accessor->_row = tv[i];
  212. RLMInitializeSwiftAccessorGenerics(accessor);
  213. [accessor setValue:value forKey:key];
  214. }
  215. }
  216. NSString *RLMDescriptionWithMaxDepth(NSString *name,
  217. id<RLMCollection> collection,
  218. NSUInteger depth) {
  219. if (depth == 0) {
  220. return @"<Maximum depth exceeded>";
  221. }
  222. const NSUInteger maxObjects = 100;
  223. auto str = [NSMutableString stringWithFormat:@"%@<%@> <%p> (\n", name,
  224. [collection objectClassName] ?: RLMTypeToString([collection type]),
  225. (void *)collection];
  226. size_t index = 0, skipped = 0;
  227. for (id obj in collection) {
  228. NSString *sub;
  229. if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
  230. sub = [obj descriptionWithMaxDepth:depth - 1];
  231. }
  232. else {
  233. sub = [obj description];
  234. }
  235. // Indent child objects
  236. NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n"
  237. withString:@"\n\t"];
  238. [str appendFormat:@"\t[%zu] %@,\n", index++, objDescription];
  239. if (index >= maxObjects) {
  240. skipped = collection.count - maxObjects;
  241. break;
  242. }
  243. }
  244. // Remove last comma and newline characters
  245. if (collection.count > 0) {
  246. [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)];
  247. }
  248. if (skipped) {
  249. [str appendFormat:@"\n\t... %zu objects skipped.", skipped];
  250. }
  251. [str appendFormat:@"\n)"];
  252. return str;
  253. }
  254. std::vector<std::pair<std::string, bool>> RLMSortDescriptorsToKeypathArray(NSArray<RLMSortDescriptor *> *properties) {
  255. std::vector<std::pair<std::string, bool>> keypaths;
  256. keypaths.reserve(properties.count);
  257. for (RLMSortDescriptor *desc in properties) {
  258. if ([desc.keyPath rangeOfString:@"@"].location != NSNotFound) {
  259. @throw RLMException(@"Cannot sort on key path '%@': KVC collection operators are not supported.", desc.keyPath);
  260. }
  261. keypaths.push_back({desc.keyPath.UTF8String, desc.ascending});
  262. }
  263. return keypaths;
  264. }
  265. @implementation RLMCancellationToken {
  266. realm::NotificationToken _token;
  267. __unsafe_unretained RLMRealm *_realm;
  268. }
  269. - (instancetype)initWithToken:(realm::NotificationToken)token realm:(RLMRealm *)realm {
  270. self = [super init];
  271. if (self) {
  272. _token = std::move(token);
  273. _realm = realm;
  274. }
  275. return self;
  276. }
  277. - (RLMRealm *)realm {
  278. return _realm;
  279. }
  280. - (void)suppressNextNotification {
  281. _token.suppress_next();
  282. }
  283. - (void)invalidate {
  284. _token = {};
  285. }
  286. @end
  287. @implementation RLMCollectionChange {
  288. realm::CollectionChangeSet _indices;
  289. }
  290. - (instancetype)initWithChanges:(realm::CollectionChangeSet)indices {
  291. self = [super init];
  292. if (self) {
  293. _indices = std::move(indices);
  294. }
  295. return self;
  296. }
  297. static NSArray *toArray(realm::IndexSet const& set) {
  298. NSMutableArray *ret = [NSMutableArray new];
  299. for (auto index : set.as_indexes()) {
  300. [ret addObject:@(index)];
  301. }
  302. return ret;
  303. }
  304. - (NSArray *)insertions {
  305. return toArray(_indices.insertions);
  306. }
  307. - (NSArray *)deletions {
  308. return toArray(_indices.deletions);
  309. }
  310. - (NSArray *)modifications {
  311. return toArray(_indices.modifications);
  312. }
  313. static NSArray *toIndexPathArray(realm::IndexSet const& set, NSUInteger section) {
  314. NSMutableArray *ret = [NSMutableArray new];
  315. NSUInteger path[2] = {section, 0};
  316. for (auto index : set.as_indexes()) {
  317. path[1] = index;
  318. [ret addObject:[NSIndexPath indexPathWithIndexes:path length:2]];
  319. }
  320. return ret;
  321. }
  322. - (NSArray<NSIndexPath *> *)deletionsInSection:(NSUInteger)section {
  323. return toIndexPathArray(_indices.deletions, section);
  324. }
  325. - (NSArray<NSIndexPath *> *)insertionsInSection:(NSUInteger)section {
  326. return toIndexPathArray(_indices.insertions, section);
  327. }
  328. - (NSArray<NSIndexPath *> *)modificationsInSection:(NSUInteger)section {
  329. return toIndexPathArray(_indices.modifications, section);
  330. }
  331. @end
  332. template<typename Collection>
  333. RLMNotificationToken *RLMAddNotificationBlock(id objcCollection,
  334. Collection& collection,
  335. void (^block)(id, RLMCollectionChange *, NSError *),
  336. bool suppressInitialChange) {
  337. auto skip = suppressInitialChange ? std::make_shared<bool>(true) : nullptr;
  338. auto cb = [=, &collection](realm::CollectionChangeSet const& changes,
  339. std::exception_ptr err) {
  340. if (err) {
  341. try {
  342. rethrow_exception(err);
  343. }
  344. catch (...) {
  345. NSError *error = nil;
  346. RLMRealmTranslateException(&error);
  347. block(nil, nil, error);
  348. return;
  349. }
  350. }
  351. if (skip && *skip) {
  352. *skip = false;
  353. block(objcCollection, nil, nil);
  354. }
  355. else if (changes.empty()) {
  356. block(objcCollection, nil, nil);
  357. }
  358. else {
  359. block(objcCollection, [[RLMCollectionChange alloc] initWithChanges:changes], nil);
  360. }
  361. };
  362. return [[RLMCancellationToken alloc] initWithToken:collection.add_notification_callback(cb)
  363. realm:(RLMRealm *)[objcCollection realm]];
  364. }
  365. // Explicitly instantiate the templated function for the two types we'll use it on
  366. template RLMNotificationToken *RLMAddNotificationBlock<realm::List>(id, realm::List&, void (^)(id, RLMCollectionChange *, NSError *), bool);
  367. template RLMNotificationToken *RLMAddNotificationBlock<realm::Results>(id, realm::Results&, void (^)(id, RLMCollectionChange *, NSError *), bool);