RLMSet.mm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2020 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 "RLMSet_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. @interface RLMSet () <RLMThreadConfined_Private>
  29. @end
  30. @implementation RLMSet {
  31. @public
  32. // Backing set when this instance is unmanaged
  33. NSMutableOrderedSet *_backingCollection;
  34. }
  35. #pragma mark - Initializers
  36. - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName
  37. keyType:(__unused RLMPropertyType)keyType {
  38. return [self initWithObjectClassName:objectClassName];
  39. }
  40. - (instancetype)initWithObjectType:(RLMPropertyType)type
  41. optional:(BOOL)optional
  42. keyType:(__unused RLMPropertyType)keyType {
  43. return [self initWithObjectType:type optional:optional];
  44. }
  45. - (instancetype)initWithObjectClassName:(__unsafe_unretained NSString *const)objectClassName {
  46. REALM_ASSERT([objectClassName length] > 0);
  47. self = [super init];
  48. if (self) {
  49. _objectClassName = objectClassName;
  50. _type = RLMPropertyTypeObject;
  51. }
  52. return self;
  53. }
  54. - (instancetype)initWithObjectType:(RLMPropertyType)type optional:(BOOL)optional {
  55. self = [super init];
  56. if (self) {
  57. _type = type;
  58. _optional = optional;
  59. }
  60. return self;
  61. }
  62. - (void)setParent:(RLMObjectBase *)parentObject property:(RLMProperty *)property {
  63. _parentObject = parentObject;
  64. _key = property.name;
  65. _isLegacyProperty = property.isLegacy;
  66. }
  67. #pragma mark - Convenience wrappers used for all RLMSet types
  68. - (void)addObjects:(id<NSFastEnumeration>)objects {
  69. for (id obj in objects) {
  70. [self addObject:obj];
  71. }
  72. }
  73. - (void)addObject:(id)object {
  74. RLMSetValidateMatchingObjectType(self, object);
  75. changeSet(self, ^{
  76. [_backingCollection addObject:object];
  77. });
  78. }
  79. - (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index {
  80. REALM_TERMINATE("Replacing objects at an indexed subscript is not supported on RLMSet");
  81. }
  82. - (void)setSet:(RLMSet<id> *)set {
  83. for (id obj in set) {
  84. RLMSetValidateMatchingObjectType(self, obj);
  85. }
  86. changeSet(self, ^{
  87. [_backingCollection removeAllObjects];
  88. [_backingCollection unionOrderedSet:set->_backingCollection];
  89. });
  90. }
  91. - (void)intersectSet:(RLMSet<id> *)set {
  92. for (id obj in set) {
  93. RLMSetValidateMatchingObjectType(self, obj);
  94. }
  95. changeSet(self, ^{
  96. [_backingCollection intersectOrderedSet:set->_backingCollection];
  97. });
  98. }
  99. - (void)minusSet:(RLMSet<id> *)set {
  100. for (id obj in set) {
  101. RLMSetValidateMatchingObjectType(self, obj);
  102. }
  103. changeSet(self, ^{
  104. [_backingCollection minusOrderedSet:set->_backingCollection];
  105. });
  106. }
  107. - (void)unionSet:(RLMSet<id> *)set {
  108. for (id obj in set) {
  109. RLMSetValidateMatchingObjectType(self, obj);
  110. }
  111. changeSet(self, ^{
  112. [_backingCollection unionOrderedSet:set->_backingCollection];
  113. });
  114. }
  115. - (BOOL)isSubsetOfSet:(RLMSet<id> *)set {
  116. for (id obj in set) {
  117. RLMSetValidateMatchingObjectType(self, obj);
  118. }
  119. return [_backingCollection isSubsetOfOrderedSet:set->_backingCollection];
  120. }
  121. - (BOOL)intersectsSet:(RLMSet<id> *)set {
  122. for (id obj in set) {
  123. RLMSetValidateMatchingObjectType(self, obj);
  124. }
  125. return [_backingCollection intersectsOrderedSet:set->_backingCollection];
  126. }
  127. - (BOOL)containsObject:(id)obj {
  128. RLMSetValidateMatchingObjectType(self, obj);
  129. return [_backingCollection containsObject:obj];
  130. }
  131. - (BOOL)isEqualToSet:(RLMSet<id> *)set {
  132. return [self isEqual:set];
  133. }
  134. - (RLMResults *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending {
  135. return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithKeyPath:keyPath ascending:ascending]]];
  136. }
  137. - (nonnull id)objectAtIndexedSubscript:(NSUInteger)index {
  138. return [self objectAtIndex:index];
  139. }
  140. // The compiler complains about the method's argument type not matching due to
  141. // it not having the generic type attached, but it doesn't seem to be possible
  142. // to actually include the generic type
  143. // http://www.openradar.me/radar?id=6135653276319744
  144. #pragma clang diagnostic push
  145. #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
  146. - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
  147. return RLMAddNotificationBlock(self, block, nil, nil);
  148. }
  149. - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block
  150. queue:(dispatch_queue_t)queue {
  151. return RLMAddNotificationBlock(self, block, nil, queue);
  152. }
  153. - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block
  154. keyPaths:(NSArray<NSString *> *)keyPaths {
  155. return RLMAddNotificationBlock(self, block, keyPaths,nil);
  156. }
  157. - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block
  158. keyPaths:(NSArray<NSString *> *)keyPaths
  159. queue:(dispatch_queue_t)queue {
  160. return RLMAddNotificationBlock(self, block, keyPaths, queue);
  161. }
  162. #pragma clang diagnostic pop
  163. #pragma mark - Unmanaged RLMSet implementation
  164. - (RLMRealm *)realm {
  165. return nil;
  166. }
  167. - (NSUInteger)count {
  168. return _backingCollection.count;
  169. }
  170. - (NSArray<id> *)allObjects {
  171. return _backingCollection.array;
  172. }
  173. // For use with MutableSet subscripting, NSSet does not support
  174. // subscripting while its Swift counterpart `Set` does.
  175. - (id)objectAtIndex:(NSUInteger)index {
  176. validateSetBounds(self, index);
  177. return _backingCollection[index];
  178. }
  179. - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
  180. if ([indexes indexGreaterThanOrEqualToIndex:self.count] != NSNotFound) {
  181. return nil;
  182. }
  183. return [_backingCollection objectsAtIndexes:indexes] ?: @[];
  184. }
  185. - (id)firstObject {
  186. return _backingCollection.firstObject;
  187. }
  188. - (id)lastObject {
  189. return _backingCollection.lastObject;
  190. }
  191. - (BOOL)isInvalidated {
  192. return NO;
  193. }
  194. - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
  195. objects:(__unused __unsafe_unretained id [])buffer
  196. count:(__unused NSUInteger)len {
  197. return RLMUnmanagedFastEnumerate(_backingCollection, state);
  198. }
  199. static void changeSet(__unsafe_unretained RLMSet *const set,
  200. dispatch_block_t f) {
  201. if (!set->_backingCollection) {
  202. set->_backingCollection = [NSMutableOrderedSet new];
  203. }
  204. if (RLMObjectBase *parent = set->_parentObject) {
  205. [parent willChangeValueForKey:set->_key];
  206. f();
  207. [parent didChangeValueForKey:set->_key];
  208. }
  209. else {
  210. f();
  211. }
  212. }
  213. static void validateSetBounds(__unsafe_unretained RLMSet *const set,
  214. NSUInteger index,
  215. bool allowOnePastEnd=false) {
  216. NSUInteger max = set->_backingCollection.count + allowOnePastEnd;
  217. if (index >= max) {
  218. @throw RLMException(@"Index %llu is out of bounds (must be less than %llu).",
  219. (unsigned long long)index, (unsigned long long)max);
  220. }
  221. }
  222. - (void)removeAllObjects {
  223. changeSet(self, ^{
  224. [_backingCollection removeAllObjects];
  225. });
  226. }
  227. - (void)removeObject:(id)object {
  228. RLMSetValidateMatchingObjectType(self, object);
  229. changeSet(self, ^{
  230. [_backingCollection removeObject:object];
  231. });
  232. }
  233. - (void)replaceAllObjectsWithObjects:(NSArray *)objects {
  234. changeSet(self, ^{
  235. [_backingCollection removeAllObjects];
  236. if (!objects || (id)objects == NSNull.null) {
  237. return;
  238. }
  239. for (id object in objects) {
  240. [_backingCollection addObject:object];
  241. }
  242. });
  243. }
  244. - (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
  245. va_list args;
  246. va_start(args, predicateFormat);
  247. RLMResults *results = [self objectsWhere:predicateFormat args:args];
  248. va_end(args);
  249. return results;
  250. }
  251. - (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
  252. return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
  253. }
  254. - (RLMPropertyType)typeForProperty:(NSString *)propertyName {
  255. if ([propertyName isEqualToString:@"self"]) {
  256. return _type;
  257. }
  258. RLMObjectSchema *objectSchema;
  259. if (_backingCollection.count) {
  260. objectSchema = [_backingCollection[0] objectSchema];
  261. }
  262. else {
  263. objectSchema = [RLMSchema.partialPrivateSharedSchema schemaForClassName:_objectClassName];
  264. }
  265. return RLMValidatedProperty(objectSchema, propertyName).type;
  266. }
  267. - (id)aggregateProperty:(NSString *)key operation:(NSString *)op method:(SEL)sel {
  268. // Although delegating to valueForKeyPath: here would allow to support
  269. // nested key paths as well, limiting functionality gives consistency
  270. // between unmanaged and managed arrays.
  271. if ([key rangeOfString:@"."].location != NSNotFound) {
  272. @throw RLMException(@"Nested key paths are not supported yet for KVC collection operators.");
  273. }
  274. if ([op isEqualToString:@"@distinctUnionOfObjects"]) {
  275. @throw RLMException(@"this class does not implement the distinctUnionOfObjects");
  276. }
  277. bool allowDate = false;
  278. bool sum = false;
  279. if ([op isEqualToString:@"@min"] || [op isEqualToString:@"@max"]) {
  280. allowDate = true;
  281. }
  282. else if ([op isEqualToString:@"@sum"]) {
  283. sum = true;
  284. }
  285. else if (![op isEqualToString:@"@avg"]) {
  286. // Just delegate to NSSet for all other operators
  287. return [_backingCollection valueForKeyPath:[op stringByAppendingPathExtension:key]];
  288. }
  289. RLMPropertyType type = [self typeForProperty:key];
  290. if (!canAggregate(type, allowDate)) {
  291. NSString *method = sel ? NSStringFromSelector(sel) : op;
  292. if (_type == RLMPropertyTypeObject) {
  293. @throw RLMException(@"%@: is not supported for %@ property '%@.%@'",
  294. method, RLMTypeToString(type), _objectClassName, key);
  295. }
  296. else {
  297. @throw RLMException(@"%@ is not supported for %@%s set",
  298. method, RLMTypeToString(_type), _optional ? "?" : "");
  299. }
  300. }
  301. // `valueForKeyPath` on NSSet will only return distinct values, which is an
  302. // issue as the realm::object_store::Set aggregate methods will calculate
  303. // the result based on each element of a property regardless of uniqueness.
  304. // To get around this we will need to use the `array` property of the NSMutableOrderedSet
  305. NSArray *values = [key isEqualToString:@"self"] ? _backingCollection.array : [_backingCollection.array valueForKey:key];
  306. if (_optional) {
  307. // Filter out NSNull values to match our behavior on managed arrays
  308. NSIndexSet *nonnull = [values indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
  309. return obj != NSNull.null;
  310. }];
  311. if (nonnull.count < values.count) {
  312. values = [values objectsAtIndexes:nonnull];
  313. }
  314. }
  315. id result = [values valueForKeyPath:[op stringByAppendingString:@".self"]];
  316. return sum && !result ? @0 : result;
  317. }
  318. static NSSet *toUnorderedSet(id value) {
  319. if (auto orderedSet = RLMDynamicCast<NSOrderedSet>(value)) {
  320. return orderedSet.set;
  321. }
  322. return value;
  323. }
  324. - (id)valueForKeyPath:(NSString *)keyPath {
  325. if ([keyPath characterAtIndex:0] != '@') {
  326. return toUnorderedSet(_backingCollection ? [_backingCollection valueForKeyPath:keyPath] : [super valueForKeyPath:keyPath]);
  327. }
  328. if (!_backingCollection) {
  329. _backingCollection = [NSMutableOrderedSet new];
  330. }
  331. NSUInteger dot = [keyPath rangeOfString:@"."].location;
  332. if (dot == NSNotFound) {
  333. return [_backingCollection valueForKeyPath:keyPath];
  334. }
  335. NSString *op = [keyPath substringToIndex:dot];
  336. NSString *key = [keyPath substringFromIndex:dot + 1];
  337. return [self aggregateProperty:key operation:op method:nil];
  338. }
  339. - (id)valueForKey:(NSString *)key {
  340. if ([key isEqualToString:RLMInvalidatedKey]) {
  341. return @NO; // Unmanaged sets are never invalidated
  342. }
  343. if (!_backingCollection) {
  344. _backingCollection = [NSMutableOrderedSet new];
  345. }
  346. return toUnorderedSet([_backingCollection valueForKey:key]);
  347. }
  348. - (void)setValue:(id)value forKey:(NSString *)key {
  349. if ([key isEqualToString:@"self"]) {
  350. RLMSetValidateMatchingObjectType(self, value);
  351. [_backingCollection removeAllObjects];
  352. [_backingCollection addObject:value];
  353. return;
  354. }
  355. else if (_type == RLMPropertyTypeObject) {
  356. [_backingCollection setValue:value forKey:key];
  357. }
  358. else {
  359. [self setValue:value forUndefinedKey:key];
  360. }
  361. }
  362. - (id)minOfProperty:(NSString *)property {
  363. return [self aggregateProperty:property operation:@"@min" method:_cmd];
  364. }
  365. - (id)maxOfProperty:(NSString *)property {
  366. return [self aggregateProperty:property operation:@"@max" method:_cmd];
  367. }
  368. - (id)sumOfProperty:(NSString *)property {
  369. return [self aggregateProperty:property operation:@"@sum" method:_cmd];
  370. }
  371. - (id)averageOfProperty:(NSString *)property {
  372. return [self aggregateProperty:property operation:@"@avg" method:_cmd];
  373. }
  374. - (BOOL)isEqual:(id)object {
  375. if (auto set = RLMDynamicCast<RLMSet>(object)) {
  376. return !set.realm
  377. && ((_backingCollection.count == 0 && set->_backingCollection.count == 0)
  378. || [_backingCollection isEqual:set->_backingCollection]);
  379. }
  380. return NO;
  381. }
  382. - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
  383. options:(NSKeyValueObservingOptions)options context:(void *)context {
  384. RLMValidateSetObservationKey(keyPath, self);
  385. [super addObserver:observer forKeyPath:keyPath options:options context:context];
  386. }
  387. void RLMSetValidateMatchingObjectType(__unsafe_unretained RLMSet *const set,
  388. __unsafe_unretained id const value) {
  389. if (!value && !set->_optional) {
  390. @throw RLMException(@"Invalid nil value for set of '%@'.",
  391. set->_objectClassName ?: RLMTypeToString(set->_type));
  392. }
  393. if (set->_type != RLMPropertyTypeObject) {
  394. if (!RLMValidateValue(value, set->_type, set->_optional, false, nil)) {
  395. @throw RLMException(@"Invalid value '%@' of type '%@' for expected type '%@%s'.",
  396. value, [value class], RLMTypeToString(set->_type),
  397. set->_optional ? "?" : "");
  398. }
  399. return;
  400. }
  401. auto object = RLMDynamicCast<RLMObjectBase>(value);
  402. if (!object) {
  403. return;
  404. }
  405. if (!object->_objectSchema) {
  406. @throw RLMException(@"Object cannot be inserted unless the schema is initialized. "
  407. "This can happen if you try to insert objects into a RLMSet / Set from a default value or from an overriden unmanaged initializer (`init()`).");
  408. }
  409. if (![set->_objectClassName isEqualToString:object->_objectSchema.className]
  410. && (set->_type != RLMPropertyTypeAny)) {
  411. @throw RLMException(@"Object of type '%@' does not match RLMSet type '%@'.",
  412. object->_objectSchema.className, set->_objectClassName);
  413. }
  414. }
  415. #pragma mark - Key Path Strings
  416. - (NSString *)propertyKey {
  417. return _key;
  418. }
  419. #pragma mark - Methods unsupported on unmanaged RLMSet instances
  420. #pragma clang diagnostic push
  421. #pragma clang diagnostic ignored "-Wunused-parameter"
  422. - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
  423. @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm");
  424. }
  425. - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
  426. @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm");
  427. }
  428. - (RLMResults *)distinctResultsUsingKeyPaths:(NSArray<NSString *> *)keyPaths {
  429. @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm");
  430. }
  431. - (RLMSectionedResults *)sectionedResultsSortedUsingKeyPath:(NSString *)keyPath
  432. ascending:(BOOL)ascending
  433. keyBlock:(RLMSectionedResultsKeyBlock)keyBlock {
  434. @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm");
  435. }
  436. - (RLMSectionedResults *)sectionedResultsUsingSortDescriptors:(NSArray<RLMSortDescriptor *> *)sortDescriptors
  437. keyBlock:(RLMSectionedResultsKeyBlock)keyBlock {
  438. @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm");
  439. }
  440. - (instancetype)freeze {
  441. @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm");
  442. }
  443. - (instancetype)thaw {
  444. @throw RLMException(@"This method may only be called on RLMSet instances retrieved from an RLMRealm");
  445. }
  446. #pragma mark - Thread Confined Protocol Conformance
  447. - (realm::ThreadSafeReference)makeThreadSafeReference {
  448. REALM_TERMINATE("Unexpected handover of unmanaged `RLMSet`");
  449. }
  450. - (id)objectiveCMetadata {
  451. REALM_TERMINATE("Unexpected handover of unmanaged `RLMSet`");
  452. }
  453. + (instancetype)objectWithThreadSafeReference:(realm::ThreadSafeReference)reference
  454. metadata:(id)metadata
  455. realm:(RLMRealm *)realm {
  456. REALM_TERMINATE("Unexpected handover of unmanaged `RLMSet`");
  457. }
  458. #pragma clang diagnostic pop // unused parameter warning
  459. #pragma mark - Superclass Overrides
  460. - (NSString *)description {
  461. return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
  462. }
  463. - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
  464. return RLMDescriptionWithMaxDepth(@"RLMSet", self, depth);
  465. }
  466. @end