RLMCollection.mm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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. classInfo:(RLMClassInfo&)info
  49. {
  50. self = [super init];
  51. if (self) {
  52. _info = &info;
  53. _realm = _info->realm;
  54. if (_realm.inWriteTransaction) {
  55. _snapshot = list.snapshot();
  56. }
  57. else {
  58. _snapshot = list.as_results();
  59. _collection = collection;
  60. [_realm registerEnumerator:self];
  61. }
  62. _results = &_snapshot;
  63. }
  64. return self;
  65. }
  66. - (instancetype)initWithResults:(realm::Results&)results
  67. collection:(id)collection
  68. classInfo:(RLMClassInfo&)info
  69. {
  70. self = [super init];
  71. if (self) {
  72. _info = &info;
  73. _realm = _info->realm;
  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. }
  84. return self;
  85. }
  86. - (void)dealloc {
  87. if (_collection) {
  88. [_realm unregisterEnumerator:self];
  89. }
  90. }
  91. - (void)detach {
  92. _snapshot = _results->snapshot();
  93. _results = &_snapshot;
  94. _collection = nil;
  95. }
  96. - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
  97. count:(NSUInteger)len {
  98. [_realm verifyThread];
  99. if (!_results->is_valid()) {
  100. @throw RLMException(@"Collection is no longer valid");
  101. }
  102. // The fast enumeration buffer size is currently a hardcoded number in the
  103. // compiler so this can't actually happen, but just in case it changes in
  104. // the future...
  105. if (len > RLMEnumerationBufferSize) {
  106. len = RLMEnumerationBufferSize;
  107. }
  108. NSUInteger batchCount = 0, count = state->extra[1];
  109. @autoreleasepool {
  110. RLMAccessorContext ctx(*_info);
  111. for (NSUInteger index = state->state; index < count && batchCount < len; ++index) {
  112. _strongBuffer[batchCount] = _results->get(ctx, index);
  113. batchCount++;
  114. }
  115. }
  116. for (NSUInteger i = batchCount; i < len; ++i) {
  117. _strongBuffer[i] = nil;
  118. }
  119. if (batchCount == 0) {
  120. // Release our data if we're done, as we're autoreleased and so may
  121. // stick around for a while
  122. if (_collection) {
  123. _collection = nil;
  124. [_realm unregisterEnumerator:self];
  125. }
  126. _snapshot = {};
  127. }
  128. state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer;
  129. state->state += batchCount;
  130. state->mutationsPtr = state->extra+1;
  131. return batchCount;
  132. }
  133. @end
  134. NSUInteger RLMFastEnumerate(NSFastEnumerationState *state, NSUInteger len, id<RLMFastEnumerable> collection) {
  135. __autoreleasing RLMFastEnumerator *enumerator;
  136. if (state->state == 0) {
  137. enumerator = collection.fastEnumerator;
  138. state->extra[0] = (long)enumerator;
  139. state->extra[1] = collection.count;
  140. }
  141. else {
  142. enumerator = (__bridge id)(void *)state->extra[0];
  143. }
  144. return [enumerator countByEnumeratingWithState:state count:len];
  145. }
  146. template<typename Collection>
  147. NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key, RLMClassInfo& info) {
  148. size_t count = collection.size();
  149. if (count == 0) {
  150. return @[];
  151. }
  152. NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
  153. if ([key isEqualToString:@"self"]) {
  154. RLMAccessorContext context(info);
  155. for (size_t i = 0; i < count; ++i) {
  156. [array addObject:collection.get(context, i) ?: NSNull.null];
  157. }
  158. return array;
  159. }
  160. if (collection.get_type() != realm::PropertyType::Object) {
  161. RLMAccessorContext context(info);
  162. for (size_t i = 0; i < count; ++i) {
  163. [array addObject:[collection.get(context, i) valueForKey:key] ?: NSNull.null];
  164. }
  165. return array;
  166. }
  167. RLMObject *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info);
  168. // List properties need to be handled specially since we need to create a
  169. // new List each time
  170. if (info.rlmObjectSchema.isSwiftClass) {
  171. auto prop = info.rlmObjectSchema[key];
  172. if (prop && prop.array && prop.swiftIvar) {
  173. // Grab the actual class for the generic List from an instance of it
  174. // so that we can make instances of the List without creating a new
  175. // object accessor each time
  176. Class cls = [object_getIvar(accessor, prop.swiftIvar) class];
  177. RLMAccessorContext context(info);
  178. for (size_t i = 0; i < count; ++i) {
  179. RLMListBase *list = [[cls alloc] init];
  180. list._rlmArray = [[RLMManagedArray alloc] initWithList:realm::List(info.realm->_realm,
  181. collection.get(i),
  182. info.tableColumn(prop))
  183. parentInfo:&info
  184. property:prop];
  185. [array addObject:list];
  186. }
  187. return array;
  188. }
  189. }
  190. for (size_t i = 0; i < count; i++) {
  191. accessor->_row = collection.get(i);
  192. RLMInitializeSwiftAccessorGenerics(accessor);
  193. [array addObject:[accessor valueForKey:key] ?: NSNull.null];
  194. }
  195. return array;
  196. }
  197. template NSArray *RLMCollectionValueForKey(realm::Results&, NSString *, RLMClassInfo&);
  198. template NSArray *RLMCollectionValueForKey(realm::List&, NSString *, RLMClassInfo&);
  199. void RLMCollectionSetValueForKey(id<RLMFastEnumerable> collection, NSString *key, id value) {
  200. realm::TableView tv = [collection tableView];
  201. if (tv.size() == 0) {
  202. return;
  203. }
  204. RLMClassInfo *info = collection.objectInfo;
  205. RLMObject *accessor = RLMCreateManagedAccessor(info->rlmObjectSchema.accessorClass, info);
  206. for (size_t i = 0; i < tv.size(); i++) {
  207. accessor->_row = tv[i];
  208. RLMInitializeSwiftAccessorGenerics(accessor);
  209. [accessor setValue:value forKey:key];
  210. }
  211. }
  212. NSString *RLMDescriptionWithMaxDepth(NSString *name,
  213. id<RLMCollection> collection,
  214. NSUInteger depth) {
  215. if (depth == 0) {
  216. return @"<Maximum depth exceeded>";
  217. }
  218. const NSUInteger maxObjects = 100;
  219. auto str = [NSMutableString stringWithFormat:@"%@<%@> <%p> (\n", name,
  220. [collection objectClassName] ?: RLMTypeToString([collection type]),
  221. (void *)collection];
  222. size_t index = 0, skipped = 0;
  223. for (id obj in collection) {
  224. NSString *sub;
  225. if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
  226. sub = [obj descriptionWithMaxDepth:depth - 1];
  227. }
  228. else {
  229. sub = [obj description];
  230. }
  231. // Indent child objects
  232. NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n"
  233. withString:@"\n\t"];
  234. [str appendFormat:@"\t[%zu] %@,\n", index++, objDescription];
  235. if (index >= maxObjects) {
  236. skipped = collection.count - maxObjects;
  237. break;
  238. }
  239. }
  240. // Remove last comma and newline characters
  241. if (collection.count > 0) {
  242. [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)];
  243. }
  244. if (skipped) {
  245. [str appendFormat:@"\n\t... %zu objects skipped.", skipped];
  246. }
  247. [str appendFormat:@"\n)"];
  248. return str;
  249. }
  250. std::vector<std::pair<std::string, bool>> RLMSortDescriptorsToKeypathArray(NSArray<RLMSortDescriptor *> *properties) {
  251. std::vector<std::pair<std::string, bool>> keypaths;
  252. keypaths.reserve(properties.count);
  253. for (RLMSortDescriptor *desc in properties) {
  254. if ([desc.keyPath rangeOfString:@"@"].location != NSNotFound) {
  255. @throw RLMException(@"Cannot sort on key path '%@': KVC collection operators are not supported.", desc.keyPath);
  256. }
  257. keypaths.push_back({desc.keyPath.UTF8String, desc.ascending});
  258. }
  259. return keypaths;
  260. }
  261. @implementation RLMCollectionChange {
  262. realm::CollectionChangeSet _indices;
  263. }
  264. - (instancetype)initWithChanges:(realm::CollectionChangeSet)indices {
  265. self = [super init];
  266. if (self) {
  267. _indices = std::move(indices);
  268. }
  269. return self;
  270. }
  271. static NSArray *toArray(realm::IndexSet const& set) {
  272. NSMutableArray *ret = [NSMutableArray new];
  273. for (auto index : set.as_indexes()) {
  274. [ret addObject:@(index)];
  275. }
  276. return ret;
  277. }
  278. - (NSArray *)insertions {
  279. return toArray(_indices.insertions);
  280. }
  281. - (NSArray *)deletions {
  282. return toArray(_indices.deletions);
  283. }
  284. - (NSArray *)modifications {
  285. return toArray(_indices.modifications);
  286. }
  287. static NSArray *toIndexPathArray(realm::IndexSet const& set, NSUInteger section) {
  288. NSMutableArray *ret = [NSMutableArray new];
  289. NSUInteger path[2] = {section, 0};
  290. for (auto index : set.as_indexes()) {
  291. path[1] = index;
  292. [ret addObject:[NSIndexPath indexPathWithIndexes:path length:2]];
  293. }
  294. return ret;
  295. }
  296. - (NSArray<NSIndexPath *> *)deletionsInSection:(NSUInteger)section {
  297. return toIndexPathArray(_indices.deletions, section);
  298. }
  299. - (NSArray<NSIndexPath *> *)insertionsInSection:(NSUInteger)section {
  300. return toIndexPathArray(_indices.insertions, section);
  301. }
  302. - (NSArray<NSIndexPath *> *)modificationsInSection:(NSUInteger)section {
  303. return toIndexPathArray(_indices.modifications, section);
  304. }
  305. - (NSString *)description {
  306. return [NSString stringWithFormat:@"<RLMCollectionChange: %p> insertions: %@, deletions: %@, modifications: %@",
  307. (__bridge void *)self, self.insertions, self.deletions, self.modifications];
  308. }
  309. @end
  310. namespace {
  311. struct CollectionCallbackWrapper {
  312. void (^block)(id, RLMCollectionChange *, NSError *);
  313. id collection;
  314. bool ignoreChangesInInitialNotification;
  315. void operator()(realm::CollectionChangeSet const& changes, std::exception_ptr err) {
  316. if (err) {
  317. try {
  318. rethrow_exception(err);
  319. }
  320. catch (...) {
  321. NSError *error = nil;
  322. RLMRealmTranslateException(&error);
  323. block(nil, nil, error);
  324. return;
  325. }
  326. }
  327. if (ignoreChangesInInitialNotification) {
  328. ignoreChangesInInitialNotification = false;
  329. block(collection, nil, nil);
  330. }
  331. else if (changes.empty()) {
  332. block(collection, nil, nil);
  333. }
  334. else {
  335. block(collection, [[RLMCollectionChange alloc] initWithChanges:changes], nil);
  336. }
  337. }
  338. };
  339. } // anonymous namespace
  340. @interface RLMCancellationToken : RLMNotificationToken
  341. @end
  342. @implementation RLMCancellationToken {
  343. @public
  344. __unsafe_unretained RLMRealm *_realm;
  345. realm::NotificationToken _token;
  346. std::mutex _mutex;
  347. }
  348. - (RLMRealm *)realm {
  349. std::lock_guard<std::mutex> lock(_mutex);
  350. return _realm;
  351. }
  352. - (void)suppressNextNotification {
  353. std::lock_guard<std::mutex> lock(_mutex);
  354. if (_realm) {
  355. _token.suppress_next();
  356. }
  357. }
  358. - (void)invalidate {
  359. std::lock_guard<std::mutex> lock(_mutex);
  360. _token = {};
  361. _realm = nil;
  362. }
  363. template<typename RLMCollection>
  364. RLMNotificationToken *RLMAddNotificationBlock(RLMCollection *collection,
  365. void (^block)(id, RLMCollectionChange *, NSError *),
  366. dispatch_queue_t queue) {
  367. RLMRealm *realm = collection.realm;
  368. if (!realm) {
  369. @throw RLMException(@"Linking objects notifications are only supported on managed objects.");
  370. }
  371. bool skipFirst = std::is_same_v<RLMCollection, RLMResults>;
  372. auto token = [[RLMCancellationToken alloc] init];
  373. if (!queue) {
  374. [realm verifyNotificationsAreSupported:true];
  375. token->_realm = realm;
  376. token->_token = RLMGetBackingCollection(collection).add_notification_callback(CollectionCallbackWrapper{block, collection, skipFirst});
  377. return token;
  378. }
  379. RLMThreadSafeReference *tsr = [RLMThreadSafeReference referenceWithThreadConfined:collection];
  380. token->_realm = realm;
  381. RLMRealmConfiguration *config = realm.configuration;
  382. dispatch_async(queue, ^{
  383. std::lock_guard<std::mutex> lock(token->_mutex);
  384. if (!token->_realm) {
  385. return;
  386. }
  387. NSError *error;
  388. RLMRealm *realm = token->_realm = [RLMRealm realmWithConfiguration:config queue:queue error:&error];
  389. if (!realm) {
  390. block(nil, nil, error);
  391. return;
  392. }
  393. RLMCollection *collection = [realm resolveThreadSafeReference:tsr];
  394. token->_token = RLMGetBackingCollection(collection).add_notification_callback(CollectionCallbackWrapper{block, collection, skipFirst});
  395. });
  396. return token;
  397. }
  398. @end
  399. // Explicitly instantiate the templated function for the two types we'll use it on
  400. template RLMNotificationToken *RLMAddNotificationBlock<>(RLMManagedArray *, void (^)(id, RLMCollectionChange *, NSError *), dispatch_queue_t);
  401. template RLMNotificationToken *RLMAddNotificationBlock<>(RLMResults *, void (^)(id, RLMCollectionChange *, NSError *), dispatch_queue_t);