RLMObjectSchema.mm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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 "RLMObjectSchema_Private.hpp"
  19. #import "RLMArray.h"
  20. #import "RLMListBase.h"
  21. #import "RLMObject_Private.h"
  22. #import "RLMProperty_Private.hpp"
  23. #import "RLMRealm_Dynamic.h"
  24. #import "RLMRealm_Private.hpp"
  25. #import "RLMSchema_Private.h"
  26. #import "RLMSwiftSupport.h"
  27. #import "RLMUtil.hpp"
  28. #import "object_schema.hpp"
  29. #import "object_store.hpp"
  30. using namespace realm;
  31. const Ivar RLMDummySwiftIvar = []() {
  32. static int dummy;
  33. return reinterpret_cast<objc_ivar *>(&dummy);
  34. }();
  35. // private properties
  36. @interface RLMObjectSchema ()
  37. @property (nonatomic, readwrite) NSDictionary<id, RLMProperty *> *allPropertiesByName;
  38. @property (nonatomic, readwrite) NSString *className;
  39. @end
  40. @implementation RLMObjectSchema {
  41. NSArray *_swiftGenericProperties;
  42. }
  43. - (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties {
  44. self = [super init];
  45. self.className = objectClassName;
  46. self.properties = properties;
  47. self.objectClass = objectClass;
  48. self.accessorClass = objectClass;
  49. self.unmanagedClass = objectClass;
  50. return self;
  51. }
  52. // return properties by name
  53. - (RLMProperty *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)key {
  54. return _allPropertiesByName[key];
  55. }
  56. // create property map when setting property array
  57. - (void)setProperties:(NSArray *)properties {
  58. _properties = properties;
  59. [self _propertiesDidChange];
  60. }
  61. - (void)setComputedProperties:(NSArray *)computedProperties {
  62. _computedProperties = computedProperties;
  63. [self _propertiesDidChange];
  64. }
  65. - (void)_propertiesDidChange {
  66. NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count + _computedProperties.count];
  67. NSUInteger index = 0;
  68. for (RLMProperty *prop in _properties) {
  69. prop.index = index++;
  70. map[prop.name] = prop;
  71. if (prop.isPrimary) {
  72. self.primaryKeyProperty = prop;
  73. }
  74. }
  75. index = 0;
  76. for (RLMProperty *prop in _computedProperties) {
  77. prop.index = index++;
  78. map[prop.name] = prop;
  79. }
  80. _allPropertiesByName = map;
  81. }
  82. - (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty {
  83. _primaryKeyProperty.isPrimary = NO;
  84. primaryKeyProperty.isPrimary = YES;
  85. _primaryKeyProperty = primaryKeyProperty;
  86. }
  87. + (instancetype)schemaForObjectClass:(Class)objectClass {
  88. RLMObjectSchema *schema = [RLMObjectSchema new];
  89. // determine classname from objectclass as className method has not yet been updated
  90. NSString *className = NSStringFromClass(objectClass);
  91. bool hasSwiftName = [RLMSwiftSupport isSwiftClassName:className];
  92. if (hasSwiftName) {
  93. className = [RLMSwiftSupport demangleClassName:className];
  94. }
  95. static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
  96. bool isSwift = hasSwiftName || [objectClass isSubclassOfClass:s_swiftObjectClass];
  97. schema.className = className;
  98. schema.objectClass = objectClass;
  99. schema.accessorClass = objectClass;
  100. schema.isSwiftClass = isSwift;
  101. // create array of RLMProperties, inserting properties of superclasses first
  102. Class cls = objectClass;
  103. Class superClass = class_getSuperclass(cls);
  104. NSArray *allProperties = @[];
  105. while (superClass && superClass != RLMObjectBase.class) {
  106. allProperties = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift]
  107. arrayByAddingObjectsFromArray:allProperties];
  108. cls = superClass;
  109. superClass = class_getSuperclass(superClass);
  110. }
  111. NSArray *persistedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
  112. return !RLMPropertyTypeIsComputed(property.type);
  113. }]];
  114. schema.properties = persistedProperties;
  115. NSArray *computedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
  116. return RLMPropertyTypeIsComputed(property.type);
  117. }]];
  118. schema.computedProperties = computedProperties;
  119. // verify that we didn't add any properties twice due to inheritance
  120. if (allProperties.count != [NSSet setWithArray:[allProperties valueForKey:@"name"]].count) {
  121. NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[allProperties valueForKey:@"name"]];
  122. NSArray *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) {
  123. return [countedPropertyNames countForObject:object] > 1;
  124. }]].allObjects;
  125. if (duplicatePropertyNames.count == 1) {
  126. @throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.firstObject, className);
  127. } else {
  128. @throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames componentsJoinedByString:@"', '"]);
  129. }
  130. }
  131. if (NSString *primaryKey = [objectClass primaryKey]) {
  132. for (RLMProperty *prop in schema.properties) {
  133. if ([primaryKey isEqualToString:prop.name]) {
  134. prop.indexed = YES;
  135. schema.primaryKeyProperty = prop;
  136. break;
  137. }
  138. }
  139. if (!schema.primaryKeyProperty) {
  140. @throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className);
  141. }
  142. if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString) {
  143. @throw RLMException(@"Property '%@' cannot be made the primary key of '%@' because it is not a 'string' or 'int' property.",
  144. primaryKey, className);
  145. }
  146. }
  147. for (RLMProperty *prop in schema.properties) {
  148. if (prop.optional && prop.array && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeLinkingObjects)) {
  149. // FIXME: message is awkward
  150. @throw RLMException(@"Property '%@.%@' cannot be made optional because optional '%@' properties are not supported.",
  151. className, prop.name, RLMTypeToString(prop.type));
  152. }
  153. }
  154. return schema;
  155. }
  156. + (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass {
  157. Class objectUtil = [objectClass objectUtilClass:isSwiftClass];
  158. NSArray *ignoredProperties = [objectUtil ignoredPropertiesForClass:objectClass];
  159. NSDictionary *linkingObjectsProperties = [objectUtil linkingObjectsPropertiesForClass:objectClass];
  160. NSDictionary *columnNameMap = [objectClass _realmColumnNames];
  161. // For Swift classes we need an instance of the object when parsing properties
  162. id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil;
  163. unsigned int count;
  164. std::unique_ptr<objc_property_t[], decltype(&free)> props(class_copyPropertyList(objectClass, &count), &free);
  165. NSMutableArray<RLMProperty *> *propArray = [NSMutableArray arrayWithCapacity:count];
  166. NSSet *indexed = [[NSSet alloc] initWithArray:[objectUtil indexedPropertiesForClass:objectClass]];
  167. for (unsigned int i = 0; i < count; i++) {
  168. NSString *propertyName = @(property_getName(props[i]));
  169. if ([ignoredProperties containsObject:propertyName]) {
  170. continue;
  171. }
  172. RLMProperty *prop = nil;
  173. if (isSwiftClass) {
  174. prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName
  175. indexed:[indexed containsObject:propertyName]
  176. linkPropertyDescriptor:linkingObjectsProperties[propertyName]
  177. property:props[i]
  178. instance:swiftObjectInstance];
  179. }
  180. else {
  181. prop = [[RLMProperty alloc] initWithName:propertyName
  182. indexed:[indexed containsObject:propertyName]
  183. linkPropertyDescriptor:linkingObjectsProperties[propertyName]
  184. property:props[i]];
  185. }
  186. if (prop) {
  187. if (columnNameMap) {
  188. prop.columnName = columnNameMap[prop.name];
  189. }
  190. [propArray addObject:prop];
  191. }
  192. }
  193. if (isSwiftClass) {
  194. [self addSwiftProperties:propArray objectUtil:objectUtil instance:swiftObjectInstance
  195. indexed:indexed nameMap:columnNameMap];
  196. }
  197. if (auto requiredProperties = [objectUtil requiredPropertiesForClass:objectClass]) {
  198. for (RLMProperty *property in propArray) {
  199. bool required = [requiredProperties containsObject:property.name];
  200. if (required && property.type == RLMPropertyTypeObject && !property.array) {
  201. @throw RLMException(@"Object properties cannot be made required, "
  202. "but '+[%@ requiredProperties]' included '%@'", objectClass, property.name);
  203. }
  204. property.optional &= !required;
  205. }
  206. }
  207. for (RLMProperty *property in propArray) {
  208. if (!property.optional && property.type == RLMPropertyTypeObject && !property.array) {
  209. @throw RLMException(@"The `%@.%@` property must be marked as being optional.",
  210. [objectClass className], property.name);
  211. }
  212. }
  213. return propArray;
  214. }
  215. + (void)addSwiftProperties:(NSMutableArray<RLMProperty *> *)propArray
  216. objectUtil:(Class)objectUtil
  217. instance:(id)instance
  218. indexed:(NSSet<NSString *> *)indexed
  219. nameMap:(NSDictionary<NSString *, NSString *> *)columnNameMap {
  220. // The property list reported to the obj-c runtime for Swift objects is
  221. // incomplete and doesn't include Swift generics like List<> and
  222. // RealmOptional<>, and is missing information for some properties that
  223. // are reported, such as the difference between `String` and `String?`. To
  224. // deal with this, we also get the properties from Swift reflection, and
  225. // merge the results.
  226. NSArray<RLMSwiftPropertyMetadata *> *props = [objectUtil getSwiftProperties:instance];
  227. if (!props) {
  228. // A Swift subclass of RLMObject, which operates under obj-c rules
  229. return;
  230. }
  231. // Track the index that we expect the next property to go in, for inserting
  232. // generic properties into the correct place
  233. NSUInteger nextIndex = 0;
  234. for (RLMSwiftPropertyMetadata *md in props) {
  235. // In theory existing should only ever be nextIndex or NSNotFound, and
  236. // this search is just a waste of time.
  237. // FIXME: verify if this is actually true
  238. NSUInteger existing = [propArray indexOfObjectPassingTest:^(RLMProperty *obj, NSUInteger, BOOL *) {
  239. return [obj.name isEqualToString:md.propertyName];
  240. }];
  241. RLMProperty *prop;
  242. switch (md.kind) {
  243. case RLMSwiftPropertyKindList: // List<>
  244. prop = [[RLMProperty alloc] initSwiftListPropertyWithName:md.propertyName instance:instance];
  245. break;
  246. case RLMSwiftPropertyKindLinkingObjects: { // LinkingObjects<>
  247. Ivar ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
  248. prop = [[RLMProperty alloc] initSwiftLinkingObjectsPropertyWithName:md.propertyName
  249. ivar:ivar
  250. objectClassName:md.className
  251. linkOriginPropertyName:md.linkedPropertyName];
  252. break;
  253. }
  254. case RLMSwiftPropertyKindOptional: {
  255. if (existing != NSNotFound) {
  256. // String?, Data?, Date? with a non-nil default value
  257. // We already know about this property from obj-c and we
  258. // defaulted to optional, so nothing to do
  259. break;
  260. }
  261. Ivar ivar;
  262. if (md.propertyType == RLMPropertyTypeString) {
  263. // FIXME: A non-@objc dynamic String? property which we
  264. // can't actually read so we're always just going to pretend it's nil
  265. // https://github.com/realm/realm-cocoa/issues/5784
  266. ivar = RLMDummySwiftIvar;
  267. }
  268. else {
  269. // RealmOptional<>
  270. ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
  271. }
  272. prop = [[RLMProperty alloc] initSwiftOptionalPropertyWithName:md.propertyName
  273. indexed:[indexed containsObject:md.propertyName]
  274. ivar:ivar
  275. propertyType:md.propertyType];
  276. break;
  277. }
  278. case RLMSwiftPropertyKindOther:
  279. case RLMSwiftPropertyKindNilLiteralOptional:
  280. // This might be a property which wasn't reported to obj-c and
  281. // isn't one of our supported generic types, in which case we
  282. // ignore it
  283. if (existing == NSNotFound) {
  284. --nextIndex;
  285. }
  286. // or it might be a String?, Data?, Date? or object field with
  287. // a nil default value
  288. else if (md.kind == RLMSwiftPropertyKindNilLiteralOptional) {
  289. RLMProperty *prop = propArray[existing];
  290. // RLMLinkingObjects properties on RLMObjects are allowed
  291. // to be optional as they'll be `nil` for unmanaged objects
  292. if (prop.type != RLMPropertyTypeLinkingObjects) {
  293. prop.optional = true;
  294. }
  295. }
  296. // or it may be some non-optional property which may have been
  297. // previously marked as optional due to that being the default
  298. // in obj-c
  299. else {
  300. propArray[existing].optional = false;
  301. }
  302. break;
  303. }
  304. if (prop) {
  305. if (columnNameMap) {
  306. prop.columnName = columnNameMap[prop.name];
  307. }
  308. [propArray insertObject:prop atIndex:nextIndex];
  309. }
  310. ++nextIndex;
  311. }
  312. }
  313. - (id)copyWithZone:(NSZone *)zone {
  314. RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init];
  315. schema->_objectClass = _objectClass;
  316. schema->_className = _className;
  317. schema->_objectClass = _objectClass;
  318. schema->_accessorClass = _objectClass;
  319. schema->_unmanagedClass = _unmanagedClass;
  320. schema->_isSwiftClass = _isSwiftClass;
  321. // call property setter to reset map and primary key
  322. schema.properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES];
  323. schema.computedProperties = [[NSArray allocWithZone:zone] initWithArray:_computedProperties copyItems:YES];
  324. return schema;
  325. }
  326. - (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema {
  327. if (objectSchema.properties.count != _properties.count) {
  328. return NO;
  329. }
  330. if (![_properties isEqualToArray:objectSchema.properties]) {
  331. return NO;
  332. }
  333. if (![_computedProperties isEqualToArray:objectSchema.computedProperties]) {
  334. return NO;
  335. }
  336. return YES;
  337. }
  338. - (NSString *)description {
  339. NSMutableString *propertiesString = [NSMutableString string];
  340. for (RLMProperty *property in self.properties) {
  341. [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
  342. }
  343. for (RLMProperty *property in self.computedProperties) {
  344. [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
  345. }
  346. return [NSString stringWithFormat:@"%@ {\n%@}", self.className, propertiesString];
  347. }
  348. - (NSString *)objectName {
  349. return [self.objectClass _realmObjectName] ?: _className;
  350. }
  351. - (realm::ObjectSchema)objectStoreCopy:(RLMSchema *)schema {
  352. ObjectSchema objectSchema;
  353. objectSchema.name = self.objectName.UTF8String;
  354. objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.columnName.UTF8String : "";
  355. for (RLMProperty *prop in _properties) {
  356. Property p = [prop objectStoreCopy:schema];
  357. p.is_primary = (prop == _primaryKeyProperty);
  358. objectSchema.persisted_properties.push_back(std::move(p));
  359. }
  360. for (RLMProperty *prop in _computedProperties) {
  361. objectSchema.computed_properties.push_back([prop objectStoreCopy:schema]);
  362. }
  363. return objectSchema;
  364. }
  365. + (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema {
  366. RLMObjectSchema *schema = [RLMObjectSchema new];
  367. schema.className = @(objectSchema.name.c_str());
  368. // create array of RLMProperties
  369. NSMutableArray *properties = [NSMutableArray arrayWithCapacity:objectSchema.persisted_properties.size()];
  370. for (const Property &prop : objectSchema.persisted_properties) {
  371. RLMProperty *property = [RLMProperty propertyForObjectStoreProperty:prop];
  372. property.isPrimary = (prop.name == objectSchema.primary_key);
  373. [properties addObject:property];
  374. }
  375. schema.properties = properties;
  376. NSMutableArray *computedProperties = [NSMutableArray arrayWithCapacity:objectSchema.computed_properties.size()];
  377. for (const Property &prop : objectSchema.computed_properties) {
  378. [computedProperties addObject:[RLMProperty propertyForObjectStoreProperty:prop]];
  379. }
  380. schema.computedProperties = computedProperties;
  381. // get primary key from realm metadata
  382. if (objectSchema.primary_key.length()) {
  383. NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()];
  384. schema.primaryKeyProperty = schema[primaryKeyString];
  385. if (!schema.primaryKeyProperty) {
  386. @throw RLMException(@"No property matching primary key '%@'", primaryKeyString);
  387. }
  388. }
  389. // for dynamic schema use vanilla RLMDynamicObject accessor classes
  390. schema.objectClass = RLMObject.class;
  391. schema.accessorClass = RLMDynamicObject.class;
  392. schema.unmanagedClass = RLMObject.class;
  393. return schema;
  394. }
  395. - (NSArray *)swiftGenericProperties {
  396. if (_swiftGenericProperties) {
  397. return _swiftGenericProperties;
  398. }
  399. // This check isn't semantically required, but avoiding accessing the local
  400. // static helps perf in the obj-c case
  401. if (!_isSwiftClass) {
  402. return _swiftGenericProperties = @[];
  403. }
  404. // Check if it's a swift class using the obj-c API
  405. static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
  406. if (![_accessorClass isSubclassOfClass:s_swiftObjectClass]) {
  407. return _swiftGenericProperties = @[];
  408. }
  409. NSMutableArray *genericProperties = [NSMutableArray new];
  410. for (RLMProperty *prop in _properties) {
  411. if (prop->_swiftIvar) {
  412. [genericProperties addObject:prop];
  413. }
  414. }
  415. // Currently all computed properties are Swift generics
  416. [genericProperties addObjectsFromArray:_computedProperties];
  417. return _swiftGenericProperties = genericProperties;
  418. }
  419. @end