RLMCollection.mm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  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 "RLMDictionary_Private.hpp"
  22. #import "RLMObjectSchema_Private.hpp"
  23. #import "RLMObjectStore.h"
  24. #import "RLMObject_Private.hpp"
  25. #import "RLMObservation.hpp"
  26. #import "RLMProperty_Private.h"
  27. #import "RLMSet_Private.hpp"
  28. #import "RLMSwiftCollectionBase.h"
  29. #import <realm/object-store/dictionary.hpp>
  30. #import <realm/object-store/list.hpp>
  31. #import <realm/object-store/results.hpp>
  32. #import <realm/object-store/set.hpp>
  33. static const int RLMEnumerationBufferSize = 16;
  34. @implementation RLMFastEnumerator {
  35. // The buffer supplied by fast enumeration does not retain the objects given
  36. // to it, but because we create objects on-demand and don't want them
  37. // autoreleased (a table can have more rows than the device has memory for
  38. // accessor objects) we need a thing to retain them.
  39. id _strongBuffer[RLMEnumerationBufferSize];
  40. RLMRealm *_realm;
  41. RLMClassInfo *_info;
  42. // A pointer to either _snapshot or a Results from the source collection,
  43. // to avoid having to copy the Results when not in a write transaction
  44. realm::Results *_results;
  45. realm::Results _snapshot;
  46. // A strong reference to the collection being enumerated to ensure it stays
  47. // alive when we're holding a pointer to a member in it
  48. id _collection;
  49. }
  50. - (instancetype)initWithBackingCollection:(realm::object_store::Collection const&)backingCollection
  51. collection:(id)collection
  52. classInfo:(RLMClassInfo&)info {
  53. self = [super init];
  54. if (self) {
  55. _info = &info;
  56. _realm = _info->realm;
  57. if (_realm.inWriteTransaction) {
  58. _snapshot = backingCollection.as_results().snapshot();
  59. }
  60. else {
  61. _snapshot = backingCollection.as_results();
  62. _collection = collection;
  63. [_realm registerEnumerator:self];
  64. }
  65. _results = &_snapshot;
  66. }
  67. return self;
  68. }
  69. - (instancetype)initWithBackingDictionary:(realm::object_store::Dictionary const&)backingDictionary
  70. dictionary:(RLMManagedDictionary *)dictionary
  71. classInfo:(RLMClassInfo&)info {
  72. self = [super init];
  73. if (self) {
  74. _info = &info;
  75. _realm = _info->realm;
  76. if (_realm.inWriteTransaction) {
  77. _snapshot = backingDictionary.get_keys().snapshot();
  78. }
  79. else {
  80. _snapshot = backingDictionary.get_keys();
  81. _collection = dictionary;
  82. [_realm registerEnumerator:self];
  83. }
  84. _results = &_snapshot;
  85. }
  86. return self;
  87. }
  88. - (instancetype)initWithResults:(realm::Results&)results
  89. collection:(id)collection
  90. classInfo:(RLMClassInfo&)info {
  91. self = [super init];
  92. if (self) {
  93. _info = &info;
  94. _realm = _info->realm;
  95. if (_realm.inWriteTransaction) {
  96. _snapshot = results.snapshot();
  97. _results = &_snapshot;
  98. }
  99. else {
  100. _results = &results;
  101. _collection = collection;
  102. [_realm registerEnumerator:self];
  103. }
  104. }
  105. return self;
  106. }
  107. - (void)dealloc {
  108. if (_collection) {
  109. [_realm unregisterEnumerator:self];
  110. }
  111. }
  112. - (void)detach {
  113. _snapshot = _results->snapshot();
  114. _results = &_snapshot;
  115. _collection = nil;
  116. }
  117. - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
  118. count:(NSUInteger)len {
  119. [_realm verifyThread];
  120. if (!_results->is_valid()) {
  121. @throw RLMException(@"Collection is no longer valid");
  122. }
  123. // The fast enumeration buffer size is currently a hardcoded number in the
  124. // compiler so this can't actually happen, but just in case it changes in
  125. // the future...
  126. if (len > RLMEnumerationBufferSize) {
  127. len = RLMEnumerationBufferSize;
  128. }
  129. NSUInteger batchCount = 0, count = state->extra[1];
  130. @autoreleasepool {
  131. RLMAccessorContext ctx(*_info);
  132. for (NSUInteger index = state->state; index < count && batchCount < len; ++index) {
  133. _strongBuffer[batchCount] = _results->get(ctx, index);
  134. batchCount++;
  135. }
  136. }
  137. for (NSUInteger i = batchCount; i < len; ++i) {
  138. _strongBuffer[i] = nil;
  139. }
  140. if (batchCount == 0) {
  141. // Release our data if we're done, as we're autoreleased and so may
  142. // stick around for a while
  143. if (_collection) {
  144. _collection = nil;
  145. [_realm unregisterEnumerator:self];
  146. }
  147. _snapshot = {};
  148. }
  149. state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer;
  150. state->state += batchCount;
  151. state->mutationsPtr = state->extra+1;
  152. return batchCount;
  153. }
  154. @end
  155. NSUInteger RLMFastEnumerate(NSFastEnumerationState *state,
  156. NSUInteger len,
  157. id<RLMCollectionPrivate> collection) {
  158. __autoreleasing RLMFastEnumerator *enumerator;
  159. if (state->state == 0) {
  160. enumerator = collection.fastEnumerator;
  161. state->extra[0] = (long)enumerator;
  162. state->extra[1] = collection.count;
  163. }
  164. else {
  165. enumerator = (__bridge id)(void *)state->extra[0];
  166. }
  167. return [enumerator countByEnumeratingWithState:state count:len];
  168. }
  169. @interface RLMArrayHolder : NSObject
  170. @end
  171. @implementation RLMArrayHolder {
  172. std::unique_ptr<id[]> items;
  173. }
  174. NSUInteger RLMUnmanagedFastEnumerate(id collection, NSFastEnumerationState *state) {
  175. if (state->state != 0) {
  176. return 0;
  177. }
  178. // We need to enumerate a copy of the backing array so that it doesn't
  179. // reflect changes made during enumeration. This copy has to be autoreleased
  180. // (since there's nowhere for us to store a strong reference), and uses
  181. // RLMArrayHolder rather than an NSArray because NSArray doesn't guarantee
  182. // that it'll use a single contiguous block of memory, and if it doesn't
  183. // we'd need to forward multiple calls to this method to the same NSArray,
  184. // which would require holding a reference to it somewhere.
  185. __autoreleasing RLMArrayHolder *copy = [[RLMArrayHolder alloc] init];
  186. copy->items = std::make_unique<id[]>([collection count]);
  187. NSUInteger i = 0;
  188. for (id object in collection) {
  189. copy->items[i++] = object;
  190. }
  191. state->itemsPtr = (__unsafe_unretained id *)(void *)copy->items.get();
  192. // needs to point to something valid, but the whole point of this is so
  193. // that it can't be changed
  194. state->mutationsPtr = state->extra;
  195. state->state = i;
  196. return i;
  197. }
  198. @end
  199. template<typename Collection>
  200. NSArray *RLMCollectionValueForKey(Collection& collection, NSString *key, RLMClassInfo& info) {
  201. size_t count = collection.size();
  202. if (count == 0) {
  203. return @[];
  204. }
  205. NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
  206. if ([key isEqualToString:@"self"]) {
  207. RLMAccessorContext context(info);
  208. for (size_t i = 0; i < count; ++i) {
  209. [array addObject:collection.get(context, i) ?: NSNull.null];
  210. }
  211. return array;
  212. }
  213. if (collection.get_type() != realm::PropertyType::Object) {
  214. RLMAccessorContext context(info);
  215. for (size_t i = 0; i < count; ++i) {
  216. [array addObject:[collection.get(context, i) valueForKey:key] ?: NSNull.null];
  217. }
  218. return array;
  219. }
  220. RLMObject *accessor = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, &info);
  221. auto prop = info.rlmObjectSchema[key];
  222. // Collection properties need to be handled specially since we need to create
  223. // a new collection each time
  224. if (info.rlmObjectSchema.isSwiftClass) {
  225. if (prop.collection && prop.swiftAccessor) {
  226. // Grab the actual class for the generic collection from an instance of it
  227. // so that we can make instances of the collection without creating a new
  228. // object accessor each time
  229. Class cls = [[prop.swiftAccessor get:prop on:accessor] class];
  230. for (size_t i = 0; i < count; ++i) {
  231. RLMSwiftCollectionBase *base = [[cls alloc] init];
  232. base._rlmCollection = [[[cls _backingCollectionType] alloc]
  233. initWithParent:collection.get(i) property:prop parentInfo:info];
  234. [array addObject:base];
  235. }
  236. return array;
  237. }
  238. }
  239. auto swiftAccessor = prop.swiftAccessor;
  240. for (size_t i = 0; i < count; i++) {
  241. accessor->_row = collection.get(i);
  242. if (swiftAccessor) {
  243. [swiftAccessor initialize:prop on:accessor];
  244. }
  245. [array addObject:[accessor valueForKey:key] ?: NSNull.null];
  246. }
  247. return array;
  248. }
  249. realm::ColKey columnForProperty(NSString *propertyName,
  250. realm::object_store::Collection const& backingCollection,
  251. RLMClassInfo *objectInfo,
  252. RLMPropertyType propertyType,
  253. RLMCollectionType collectionType) {
  254. if (backingCollection.get_type() == realm::PropertyType::Object) {
  255. return objectInfo->tableColumn(propertyName);
  256. }
  257. if (![propertyName isEqualToString:@"self"]) {
  258. NSString *collectionTypeName;
  259. switch (collectionType) {
  260. case RLMCollectionTypeArray:
  261. collectionTypeName = @"Arrays";
  262. break;
  263. case RLMCollectionTypeSet:
  264. collectionTypeName = @"Sets";
  265. break;
  266. case RLMCollectionTypeDictionary:
  267. collectionTypeName = @"Dictionaries";
  268. break;
  269. }
  270. @throw RLMException(@"%@ of '%@' can only be aggregated on \"self\"",
  271. collectionTypeName, RLMTypeToString(propertyType));
  272. }
  273. return {};
  274. }
  275. template NSArray *RLMCollectionValueForKey(realm::Results&, NSString *, RLMClassInfo&);
  276. template NSArray *RLMCollectionValueForKey(realm::List&, NSString *, RLMClassInfo&);
  277. template NSArray *RLMCollectionValueForKey(realm::object_store::Set&, NSString *, RLMClassInfo&);
  278. void RLMCollectionSetValueForKey(id<RLMCollectionPrivate> collection, NSString *key, id value) {
  279. realm::TableView tv = [collection tableView];
  280. if (tv.size() == 0) {
  281. return;
  282. }
  283. RLMClassInfo *info = collection.objectInfo;
  284. RLMObject *accessor = RLMCreateManagedAccessor(info->rlmObjectSchema.accessorClass, info);
  285. for (size_t i = 0; i < tv.size(); i++) {
  286. accessor->_row = tv[i];
  287. RLMInitializeSwiftAccessor(accessor, false);
  288. [accessor setValue:value forKey:key];
  289. }
  290. }
  291. void RLMAssignToCollection(id<RLMCollection> collection, id value) {
  292. [(id)collection replaceAllObjectsWithObjects:value];
  293. }
  294. NSString *RLMDescriptionWithMaxDepth(NSString *name,
  295. id<RLMCollection> collection,
  296. NSUInteger depth) {
  297. if (depth == 0) {
  298. return @"<Maximum depth exceeded>";
  299. }
  300. const NSUInteger maxObjects = 100;
  301. auto str = [NSMutableString stringWithFormat:@"%@<%@> <%p> (\n", name,
  302. [collection objectClassName] ?: RLMTypeToString([collection type]),
  303. (void *)collection];
  304. size_t index = 0, skipped = 0;
  305. for (id obj in collection) {
  306. NSString *sub;
  307. if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
  308. sub = [obj descriptionWithMaxDepth:depth - 1];
  309. }
  310. else {
  311. sub = [obj description];
  312. }
  313. // Indent child objects
  314. NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n"
  315. withString:@"\n\t"];
  316. [str appendFormat:@"\t[%zu] %@,\n", index++, objDescription];
  317. if (index >= maxObjects) {
  318. skipped = collection.count - maxObjects;
  319. break;
  320. }
  321. }
  322. // Remove last comma and newline characters
  323. if (collection.count > 0) {
  324. [str deleteCharactersInRange:NSMakeRange(str.length-2, 2)];
  325. }
  326. if (skipped) {
  327. [str appendFormat:@"\n\t... %zu objects skipped.", skipped];
  328. }
  329. [str appendFormat:@"\n)"];
  330. return str;
  331. }
  332. std::vector<std::pair<std::string, bool>> RLMSortDescriptorsToKeypathArray(NSArray<RLMSortDescriptor *> *properties) {
  333. std::vector<std::pair<std::string, bool>> keypaths;
  334. keypaths.reserve(properties.count);
  335. for (RLMSortDescriptor *desc in properties) {
  336. if ([desc.keyPath rangeOfString:@"@"].location != NSNotFound) {
  337. @throw RLMException(@"Cannot sort on key path '%@': KVC collection operators are not supported.", desc.keyPath);
  338. }
  339. keypaths.push_back({desc.keyPath.UTF8String, desc.ascending});
  340. }
  341. return keypaths;
  342. }
  343. @implementation RLMCollectionChange {
  344. realm::CollectionChangeSet _indices;
  345. }
  346. - (instancetype)initWithChanges:(realm::CollectionChangeSet)indices {
  347. self = [super init];
  348. if (self) {
  349. _indices = std::move(indices);
  350. }
  351. return self;
  352. }
  353. static NSArray *toArray(realm::IndexSet const& set) {
  354. NSMutableArray *ret = [NSMutableArray new];
  355. for (auto index : set.as_indexes()) {
  356. [ret addObject:@(index)];
  357. }
  358. return ret;
  359. }
  360. - (NSArray *)insertions {
  361. return toArray(_indices.insertions);
  362. }
  363. - (NSArray *)deletions {
  364. return toArray(_indices.deletions);
  365. }
  366. - (NSArray *)modifications {
  367. return toArray(_indices.modifications);
  368. }
  369. - (NSArray<NSIndexPath *> *)deletionsInSection:(NSUInteger)section {
  370. return RLMToIndexPathArray(_indices.deletions, section);
  371. }
  372. - (NSArray<NSIndexPath *> *)insertionsInSection:(NSUInteger)section {
  373. return RLMToIndexPathArray(_indices.insertions, section);
  374. }
  375. - (NSArray<NSIndexPath *> *)modificationsInSection:(NSUInteger)section {
  376. return RLMToIndexPathArray(_indices.modifications, section);
  377. }
  378. - (NSString *)description {
  379. return [NSString stringWithFormat:@"<RLMCollectionChange: %p> insertions: %@, deletions: %@, modifications: %@",
  380. (__bridge void *)self, self.insertions, self.deletions, self.modifications];
  381. }
  382. @end
  383. namespace {
  384. struct CollectionCallbackWrapper {
  385. void (^block)(id, id, NSError *);
  386. id collection;
  387. bool ignoreChangesInInitialNotification;
  388. void operator()(realm::CollectionChangeSet const& changes) {
  389. if (ignoreChangesInInitialNotification) {
  390. ignoreChangesInInitialNotification = false;
  391. block(collection, nil, nil);
  392. }
  393. else if (changes.empty()) {
  394. block(collection, nil, nil);
  395. }
  396. else if (!changes.collection_root_was_deleted || !changes.deletions.empty()) {
  397. block(collection, [[RLMCollectionChange alloc] initWithChanges:changes], nil);
  398. }
  399. }
  400. };
  401. } // anonymous namespace
  402. @interface RLMCancellationToken : RLMNotificationToken
  403. @end
  404. RLM_HIDDEN
  405. @implementation RLMCancellationToken {
  406. __unsafe_unretained RLMRealm *_realm;
  407. realm::NotificationToken _token;
  408. RLMUnfairMutex _mutex;
  409. }
  410. - (RLMRealm *)realm {
  411. std::lock_guard lock(_mutex);
  412. return _realm;
  413. }
  414. - (void)suppressNextNotification {
  415. std::lock_guard lock(_mutex);
  416. if (_realm) {
  417. _token.suppress_next();
  418. }
  419. }
  420. - (bool)invalidate {
  421. std::lock_guard lock(_mutex);
  422. if (_realm) {
  423. _token = {};
  424. _realm = nil;
  425. return true;
  426. }
  427. return false;
  428. }
  429. RLMNotificationToken *RLMAddNotificationBlock(id c, id block,
  430. NSArray<NSString *> *keyPaths,
  431. dispatch_queue_t queue) {
  432. id<RLMThreadConfined, RLMCollectionPrivate> collection = c;
  433. RLMRealm *realm = collection.realm;
  434. if (!realm) {
  435. @throw RLMException(@"Change notifications are only supported on managed collections.");
  436. }
  437. auto token = [[RLMCancellationToken alloc] init];
  438. token->_realm = realm;
  439. RLMClassInfo *info = collection.objectInfo;
  440. if (!queue) {
  441. [realm verifyNotificationsAreSupported:true];
  442. token->_token = [collection addNotificationCallback:block keyPaths:info->keyPathArrayFromStringArray(keyPaths)];
  443. return token;
  444. }
  445. RLMThreadSafeReference *tsr = [RLMThreadSafeReference referenceWithThreadConfined:collection];
  446. RLMRealmConfiguration *config = realm.configuration;
  447. dispatch_async(queue, ^{
  448. std::lock_guard lock(token->_mutex);
  449. if (!token->_realm) {
  450. return;
  451. }
  452. RLMRealm *realm = token->_realm = [RLMRealm realmWithConfiguration:config queue:queue error:nil];
  453. id collection = [realm resolveThreadSafeReference:tsr];
  454. token->_token = [collection addNotificationCallback:block keyPaths:info->keyPathArrayFromStringArray(keyPaths)];
  455. });
  456. return token;
  457. }
  458. realm::CollectionChangeCallback RLMWrapCollectionChangeCallback(void (^block)(id, id, NSError *),
  459. id collection, bool skipFirst) {
  460. return CollectionCallbackWrapper{block, collection, skipFirst};
  461. }
  462. @end
  463. NSArray *RLMToIndexPathArray(realm::IndexSet const& set, NSUInteger section) {
  464. NSMutableArray *ret = [NSMutableArray new];
  465. NSUInteger path[2] = {section, 0};
  466. for (auto index : set.as_indexes()) {
  467. path[1] = index;
  468. [ret addObject:[NSIndexPath indexPathWithIndexes:path length:2]];
  469. }
  470. return ret;
  471. }