RLMArray.mm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2014 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 "RLMArray_Private.hpp"
  19. #import "RLMObjectSchema.h"
  20. #import "RLMObjectStore.h"
  21. #import "RLMObject_Private.h"
  22. #import "RLMProperty_Private.h"
  23. #import "RLMQueryUtil.hpp"
  24. #import "RLMSchema_Private.h"
  25. #import "RLMSwiftSupport.h"
  26. #import "RLMThreadSafeReference_Private.hpp"
  27. #import "RLMUtil.hpp"
  28. // See -countByEnumeratingWithState:objects:count
  29. @interface RLMArrayHolder : NSObject {
  30. @public
  31. std::unique_ptr<id[]> items;
  32. }
  33. @end
  34. @implementation RLMArrayHolder
  35. @end
  36. @interface RLMArray () <RLMThreadConfined_Private>
  37. @end
  38. @implementation RLMArray {
  39. @public
  40. // Backing array when this instance is unmanaged
  41. NSMutableArray *_backingArray;
  42. }
  43. #pragma mark - Initializers
  44. - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName {
  45. REALM_ASSERT([objectClassName length] > 0);
  46. self = [super init];
  47. if (self) {
  48. _objectClassName = objectClassName;
  49. _type = RLMPropertyTypeObject;
  50. }
  51. return self;
  52. }
  53. - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional {
  54. self = [super init];
  55. if (self) {
  56. _type = type;
  57. _optional = optional;
  58. }
  59. return self;
  60. }
  61. #pragma mark - Convenience wrappers used for all RLMArray types
  62. - (void)addObjects:(id<NSFastEnumeration>)objects {
  63. for (id obj in objects) {
  64. [self addObject:obj];
  65. }
  66. }
  67. - (void)addObject:(id)object {
  68. [self insertObject:object atIndex:self.count];
  69. }
  70. - (void)removeLastObject {
  71. NSUInteger count = self.count;
  72. if (count) {
  73. [self removeObjectAtIndex:count-1];
  74. }
  75. }
  76. - (id)objectAtIndexedSubscript:(NSUInteger)index {
  77. return [self objectAtIndex:index];
  78. }
  79. - (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index {
  80. [self replaceObjectAtIndex:index withObject:newValue];
  81. }
  82. - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
  83. return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]];
  84. }
  85. - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... {
  86. va_list args;
  87. va_start(args, predicateFormat);
  88. NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args];
  89. va_end(args);
  90. return index;
  91. }
  92. - (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args {
  93. return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat
  94. arguments:args]];
  95. }
  96. #pragma mark - Unmanaged RLMArray implementation
  97. - (RLMRealm *)realm {
  98. return nil;
  99. }
  100. - (id)firstObject {
  101. if (self.count) {
  102. return [self objectAtIndex:0];
  103. }
  104. return nil;
  105. }
  106. - (id)lastObject {
  107. NSUInteger count = self.count;
  108. if (count) {
  109. return [self objectAtIndex:count-1];
  110. }
  111. return nil;
  112. }
  113. - (id)objectAtIndex:(NSUInteger)index {
  114. validateArrayBounds(self, index);
  115. return [_backingArray objectAtIndex:index];
  116. }
  117. - (NSUInteger)count {
  118. return _backingArray.count;
  119. }
  120. - (BOOL)isInvalidated {
  121. return NO;
  122. }
  123. - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
  124. objects:(__unused __unsafe_unretained id [])buffer
  125. count:(__unused NSUInteger)len {
  126. if (state->state != 0) {
  127. return 0;
  128. }
  129. // We need to enumerate a copy of the backing array so that it doesn't
  130. // reflect changes made during enumeration. This copy has to be autoreleased
  131. // (since there's nowhere for us to store a strong reference), and uses
  132. // RLMArrayHolder rather than an NSArray because NSArray doesn't guarantee
  133. // that it'll use a single contiguous block of memory, and if it doesn't
  134. // we'd need to forward multiple calls to this method to the same NSArray,
  135. // which would require holding a reference to it somewhere.
  136. __autoreleasing RLMArrayHolder *copy = [[RLMArrayHolder alloc] init];
  137. copy->items = std::make_unique<id[]>(self.count);
  138. NSUInteger i = 0;
  139. for (id object in _backingArray) {
  140. copy->items[i++] = object;
  141. }
  142. state->itemsPtr = (__unsafe_unretained id *)(void *)copy->items.get();
  143. // needs to point to something valid, but the whole point of this is so
  144. // that it can't be changed
  145. state->mutationsPtr = state->extra;
  146. state->state = i;
  147. return i;
  148. }
  149. template<typename IndexSetFactory>
  150. static void changeArray(__unsafe_unretained RLMArray *const ar,
  151. NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
  152. if (!ar->_backingArray) {
  153. ar->_backingArray = [NSMutableArray new];
  154. }
  155. if (RLMObjectBase *parent = ar->_parentObject) {
  156. NSIndexSet *indexes = is();
  157. [parent willChange:kind valuesAtIndexes:indexes forKey:ar->_key];
  158. f();
  159. [parent didChange:kind valuesAtIndexes:indexes forKey:ar->_key];
  160. }
  161. else {
  162. f();
  163. }
  164. }
  165. static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
  166. NSUInteger index, dispatch_block_t f) {
  167. changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
  168. }
  169. static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
  170. NSRange range, dispatch_block_t f) {
  171. changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
  172. }
  173. static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind,
  174. NSIndexSet *is, dispatch_block_t f) {
  175. changeArray(ar, kind, f, [=] { return is; });
  176. }
  177. void RLMArrayValidateMatchingObjectType(__unsafe_unretained RLMArray *const array,
  178. __unsafe_unretained id const value) {
  179. if (!value && !array->_optional) {
  180. @throw RLMException(@"Invalid nil value for array of '%@'.",
  181. array->_objectClassName ?: RLMTypeToString(array->_type));
  182. }
  183. if (array->_type != RLMPropertyTypeObject) {
  184. if (!RLMValidateValue(value, array->_type, array->_optional, false, nil)) {
  185. @throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@%s'.",
  186. value, [value class], RLMTypeToString(array->_type),
  187. array->_optional ? "?" : "");
  188. }
  189. return;
  190. }
  191. auto object = RLMDynamicCast<RLMObjectBase>(value);
  192. if (!object) {
  193. return;
  194. }
  195. if (!object->_objectSchema) {
  196. @throw RLMException(@"Object cannot be inserted unless the schema is initialized. "
  197. "This can happen if you try to insert objects into a RLMArray / List from a default value or from an overriden unmanaged initializer (`init()`).");
  198. }
  199. if (![array->_objectClassName isEqualToString:object->_objectSchema.className]) {
  200. @throw RLMException(@"Object of type '%@' does not match RLMArray type '%@'.",
  201. object->_objectSchema.className, array->_objectClassName);
  202. }
  203. }
  204. static void validateArrayBounds(__unsafe_unretained RLMArray *const ar,
  205. NSUInteger index, bool allowOnePastEnd=false) {
  206. NSUInteger max = ar->_backingArray.count + allowOnePastEnd;
  207. if (index >= max) {
  208. @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).",
  209. (unsigned long long)index, (unsigned long long)max);
  210. }
  211. }
  212. - (void)addObjectsFromArray:(NSArray *)array {
  213. for (id obj in array) {
  214. RLMArrayValidateMatchingObjectType(self, obj);
  215. }
  216. changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(_backingArray.count, array.count), ^{
  217. [_backingArray addObjectsFromArray:array];
  218. });
  219. }
  220. - (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
  221. RLMArrayValidateMatchingObjectType(self, anObject);
  222. validateArrayBounds(self, index, true);
  223. changeArray(self, NSKeyValueChangeInsertion, index, ^{
  224. [_backingArray insertObject:anObject atIndex:index];
  225. });
  226. }
  227. - (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
  228. changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
  229. NSUInteger currentIndex = [indexes firstIndex];
  230. for (RLMObject *obj in objects) {
  231. RLMArrayValidateMatchingObjectType(self, obj);
  232. [_backingArray insertObject:obj atIndex:currentIndex];
  233. currentIndex = [indexes indexGreaterThanIndex:currentIndex];
  234. }
  235. });
  236. }
  237. - (void)removeObjectAtIndex:(NSUInteger)index {
  238. validateArrayBounds(self, index);
  239. changeArray(self, NSKeyValueChangeRemoval, index, ^{
  240. [_backingArray removeObjectAtIndex:index];
  241. });
  242. }
  243. - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
  244. changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
  245. [_backingArray removeObjectsAtIndexes:indexes];
  246. });
  247. }
  248. - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
  249. RLMArrayValidateMatchingObjectType(self, anObject);
  250. validateArrayBounds(self, index);
  251. changeArray(self, NSKeyValueChangeReplacement, index, ^{
  252. [_backingArray replaceObjectAtIndex:index withObject:anObject];
  253. });
  254. }
  255. - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
  256. validateArrayBounds(self, sourceIndex);
  257. validateArrayBounds(self, destinationIndex);
  258. id original = _backingArray[sourceIndex];
  259. auto start = std::min(sourceIndex, destinationIndex);
  260. auto len = std::max(sourceIndex, destinationIndex) - start + 1;
  261. changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
  262. [_backingArray removeObjectAtIndex:sourceIndex];
  263. [_backingArray insertObject:original atIndex:destinationIndex];
  264. });
  265. }
  266. - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
  267. validateArrayBounds(self, index1);
  268. validateArrayBounds(self, index2);
  269. changeArray(self, NSKeyValueChangeReplacement, ^{
  270. [_backingArray exchangeObjectAtIndex:index1 withObjectAtIndex:index2];
  271. }, [=] {
  272. NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
  273. [set addIndex:index2];
  274. return set;
  275. });
  276. }
  277. - (NSUInteger)indexOfObject:(id)object {
  278. RLMArrayValidateMatchingObjectType(self, object);
  279. if (!_backingArray) {
  280. return NSNotFound;
  281. }
  282. if (_type != RLMPropertyTypeObject) {
  283. return [_backingArray indexOfObject:object];
  284. }
  285. NSUInteger index = 0;
  286. for (RLMObjectBase *cmp in _backingArray) {
  287. if (RLMObjectBaseAreEqual(object, cmp)) {
  288. return index;
  289. }
  290. index++;
  291. }
  292. return NSNotFound;
  293. }
  294. - (void)removeAllObjects {
  295. changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingArray.count), ^{
  296. [_backingArray removeAllObjects];
  297. });
  298. }
  299. - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
  300. va_list args;
  301. va_start(args, predicateFormat);
  302. RLMResults *results = [self objectsWhere:predicateFormat args:args];
  303. va_end(args);
  304. return results;
  305. }
  306. - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
  307. return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
  308. }
  309. static bool canAggregate(RLMPropertyType type, bool allowDate) {
  310. switch (type) {
  311. case RLMPropertyTypeInt:
  312. case RLMPropertyTypeFloat:
  313. case RLMPropertyTypeDouble:
  314. return true;
  315. case RLMPropertyTypeDate:
  316. return allowDate;
  317. default:
  318. return false;
  319. }
  320. }
  321. - (RLMPropertyType)typeForProperty:(NSString *)propertyName {
  322. if ([propertyName isEqualToString:@"self"]) {
  323. return _type;
  324. }
  325. RLMObjectSchema *objectSchema;
  326. if (_backingArray.count) {
  327. objectSchema = [_backingArray[0] objectSchema];
  328. }
  329. else {
  330. objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName];
  331. }
  332. return RLMValidatedProperty(objectSchema, propertyName).type;
  333. }
  334. - (id)aggregateProperty:(NSString *)key operation:(NSString *)op method:(SEL)sel {
  335. // Although delegating to valueForKeyPath: here would allow to support
  336. // nested key paths as well, limiting functionality gives consistency
  337. // between unmanaged and managed arrays.
  338. if ([key rangeOfString:@"."].location != NSNotFound) {
  339. @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators.");
  340. }
  341. bool allowDate = false;
  342. bool sum = false;
  343. if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) {
  344. allowDate = true;
  345. }
  346. else if ([op isEqualToString:@"@sum"]) {
  347. sum = true;
  348. }
  349. else if (![op isEqualToString:@"@avg"]) {
  350. // Just delegate to NSArray for all other operators
  351. return [_backingArray valueForKeyPath:[op stringByAppendingPathExtension:key]];
  352. }
  353. RLMPropertyType type = [self typeForProperty:key];
  354. if (!canAggregate(type, allowDate)) {
  355. NSString *method = sel ? NSStringFromSelector(sel) : op;
  356. if (_type == RLMPropertyTypeObject) {
  357. @throw RLMException(@"%@: is not supported for %@ property '%@.%@'",
  358. method, RLMTypeToString(type), _objectClassName, key);
  359. }
  360. else {
  361. @throw RLMException(@"%@ is not supported for %@%s array",
  362. method, RLMTypeToString(_type), _optional ? "?" : "");
  363. }
  364. }
  365. NSArray *values = [key isEqualToString:@"self"] ? _backingArray : [_backingArray valueForKey:key];
  366. if (_optional) {
  367. // Filter out NSNull values to match our behavior on managed arrays
  368. NSIndexSet *nonnull = [values indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
  369. return obj != NSNull.null;
  370. }];
  371. if (nonnull.count < values.count) {
  372. values = [values objectsAtIndexes:nonnull];
  373. }
  374. }
  375. id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]];
  376. return sum && !result ? @0 : result;
  377. }
  378. - (id)valueForKeyPath:(NSString *)keyPath {
  379. if ([keyPath characterAtIndex:0] != '@') {
  380. return _backingArray ? [_backingArray valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath];
  381. }
  382. if (!_backingArray) {
  383. _backingArray = [NSMutableArray new];
  384. }
  385. NSUInteger dot = [keyPath rangeOfString:@"."].location;
  386. if (dot == NSNotFound) {
  387. return [_backingArray valueForKeyPath:keyPath];
  388. }
  389. NSString *op = [keyPath substringToIndex:dot];
  390. NSString *key = [keyPath substringFromIndex:dot + 1];
  391. return [self aggregateProperty:key operation:op method:nil];
  392. }
  393. - (id)valueForKey:(NSString *)key {
  394. if ([key isEqualToString:RLMInvalidatedKey]) {
  395. return @NO; // Unmanaged arrays are never invalidated
  396. }
  397. if (!_backingArray) {
  398. _backingArray = [NSMutableArray new];
  399. }
  400. return [_backingArray valueForKey:key];
  401. }
  402. - (void)setValue:(id)value forKey:(NSString *)key {
  403. if ([key isEqualToString:@"self"]) {
  404. RLMArrayValidateMatchingObjectType(self, value);
  405. for (NSUInteger i = 0, count = _backingArray.count; i < count; ++i) {
  406. _backingArray[i] = value;
  407. }
  408. return;
  409. }
  410. else if (_type == RLMPropertyTypeObject) {
  411. [_backingArray setValue:value forKey:key];
  412. }
  413. else {
  414. [self setValue:value forUndefinedKey:key];
  415. }
  416. }
  417. - (id)minOfProperty:(NSString *)property {
  418. return [self aggregateProperty:property operation:@"@min" method:_cmd];
  419. }
  420. - (id)maxOfProperty:(NSString *)property {
  421. return [self aggregateProperty:property operation:@"@max" method:_cmd];
  422. }
  423. - (id)sumOfProperty:(NSString *)property {
  424. return [self aggregateProperty:property operation:@"@sum" method:_cmd];
  425. }
  426. - (id)averageOfProperty:(NSString *)property {
  427. return [self aggregateProperty:property operation:@"@avg" method:_cmd];
  428. }
  429. - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
  430. if (!_backingArray) {
  431. return NSNotFound;
  432. }
  433. return [_backingArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
  434. return [predicate evaluateWithObject:obj];
  435. }];
  436. }
  437. - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
  438. if (!_backingArray) {
  439. _backingArray = [NSMutableArray new];
  440. }
  441. return [_backingArray objectsAtIndexes:indexes];
  442. }
  443. - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
  444. options:(NSKeyValueObservingOptions)options context:(void *)context {
  445. RLMValidateArrayObservationKey(keyPath, self);
  446. [super addObserver:observer forKeyPath:keyPath options:options context:context];
  447. }
  448. #pragma mark - Methods unsupported on unmanaged RLMArray instances
  449. #pragma clang diagnostic push
  450. #pragma clang diagnostic ignored "-Wunused-parameter"
  451. - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
  452. @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
  453. }
  454. - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
  455. @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
  456. }
  457. // The compiler complains about the method's argument type not matching due to
  458. // it not having the generic type attached, but it doesn't seem to be possible
  459. // to actually include the generic type
  460. // http://www.openradar.me/radar?id=6135653276319744
  461. #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
  462. - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
  463. @throw RLMException(@"This method may only be called on RLMArray instances retrieved from an RLMRealm");
  464. }
  465. #pragma mark - Thread Confined Protocol Conformance
  466. - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
  467. REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
  468. }
  469. - (id)objectiveCMetadata {
  470. REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
  471. }
  472. + (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
  473. metadata:(id)metadata
  474. realm:(RLMRealm *)realm {
  475. REALM_TERMINATE("Unexpected handover of unmanaged `RLMArray`");
  476. }
  477. #pragma clang diagnostic pop // unused parameter warning
  478. #pragma mark - Superclass Overrides
  479. - (NSString *)description {
  480. return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
  481. }
  482. - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
  483. return RLMDescriptionWithMaxDepth(@"RLMArray", self, depth);
  484. }
  485. @end
  486. @implementation RLMSortDescriptor
  487. + (instancetype)sortDescriptorWithKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
  488. RLMSortDescriptor *desc = [[RLMSortDescriptor alloc] init];
  489. desc->_keyPath = keyPath;
  490. desc->_ascending = ascending;
  491. return desc;
  492. }
  493. - (instancetype)reversedSortDescriptor {
  494. return [self.class sortDescriptorWithKeyPath:_keyPath ascending:!_ascending];
  495. }
  496. @end