RLMObjectSchema.mm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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. // private properties
  32. @interface RLMObjectSchema ()
  33. @property (nonatomic, readwrite) NSDictionary<id, RLMProperty *> *allPropertiesByName;
  34. @property (nonatomic, readwrite) NSString *className;
  35. @end
  36. @implementation RLMObjectSchema {
  37. NSArray *_swiftGenericProperties;
  38. }
  39. - (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties {
  40. self = [super init];
  41. self.className = objectClassName;
  42. self.properties = properties;
  43. self.objectClass = objectClass;
  44. self.accessorClass = objectClass;
  45. self.unmanagedClass = objectClass;
  46. return self;
  47. }
  48. // return properties by name
  49. - (RLMProperty *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)key {
  50. return _allPropertiesByName[key];
  51. }
  52. // create property map when setting property array
  53. - (void)setProperties:(NSArray *)properties {
  54. _properties = properties;
  55. [self _propertiesDidChange];
  56. }
  57. - (void)setComputedProperties:(NSArray *)computedProperties {
  58. _computedProperties = computedProperties;
  59. [self _propertiesDidChange];
  60. }
  61. - (void)_propertiesDidChange {
  62. NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count + _computedProperties.count];
  63. NSUInteger index = 0;
  64. for (RLMProperty *prop in _properties) {
  65. prop.index = index++;
  66. map[prop.name] = prop;
  67. if (prop.isPrimary) {
  68. self.primaryKeyProperty = prop;
  69. }
  70. }
  71. index = 0;
  72. for (RLMProperty *prop in _computedProperties) {
  73. prop.index = index++;
  74. map[prop.name] = prop;
  75. }
  76. _allPropertiesByName = map;
  77. }
  78. - (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty {
  79. _primaryKeyProperty.isPrimary = NO;
  80. primaryKeyProperty.isPrimary = YES;
  81. _primaryKeyProperty = primaryKeyProperty;
  82. }
  83. + (instancetype)schemaForObjectClass:(Class)objectClass {
  84. RLMObjectSchema *schema = [RLMObjectSchema new];
  85. // determine classname from objectclass as className method has not yet been updated
  86. NSString *className = NSStringFromClass(objectClass);
  87. bool hasSwiftName = [RLMSwiftSupport isSwiftClassName:className];
  88. if (hasSwiftName) {
  89. className = [RLMSwiftSupport demangleClassName:className];
  90. }
  91. static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
  92. bool isSwift = hasSwiftName || [objectClass isSubclassOfClass:s_swiftObjectClass];
  93. schema.className = className;
  94. schema.objectClass = objectClass;
  95. schema.accessorClass = objectClass;
  96. schema.isSwiftClass = isSwift;
  97. // create array of RLMProperties, inserting properties of superclasses first
  98. Class cls = objectClass;
  99. Class superClass = class_getSuperclass(cls);
  100. NSArray *allProperties = @[];
  101. while (superClass && superClass != RLMObjectBase.class) {
  102. allProperties = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift]
  103. arrayByAddingObjectsFromArray:allProperties];
  104. cls = superClass;
  105. superClass = class_getSuperclass(superClass);
  106. }
  107. NSArray *persistedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
  108. return !RLMPropertyTypeIsComputed(property.type);
  109. }]];
  110. schema.properties = persistedProperties;
  111. NSArray *computedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
  112. return RLMPropertyTypeIsComputed(property.type);
  113. }]];
  114. schema.computedProperties = computedProperties;
  115. // verify that we didn't add any properties twice due to inheritance
  116. if (allProperties.count != [NSSet setWithArray:[allProperties valueForKey:@"name"]].count) {
  117. NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[allProperties valueForKey:@"name"]];
  118. NSArray *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) {
  119. return [countedPropertyNames countForObject:object] > 1;
  120. }]].allObjects;
  121. if (duplicatePropertyNames.count == 1) {
  122. @throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.firstObject, className);
  123. } else {
  124. @throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames componentsJoinedByString:@"', '"]);
  125. }
  126. }
  127. if (NSString *primaryKey = [objectClass primaryKey]) {
  128. for (RLMProperty *prop in schema.properties) {
  129. if ([primaryKey isEqualToString:prop.name]) {
  130. prop.indexed = YES;
  131. schema.primaryKeyProperty = prop;
  132. break;
  133. }
  134. }
  135. if (!schema.primaryKeyProperty) {
  136. @throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className);
  137. }
  138. if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString) {
  139. @throw RLMException(@"Property '%@' cannot be made the primary key of '%@' because it is not a 'string' or 'int' property.",
  140. primaryKey, className);
  141. }
  142. }
  143. for (RLMProperty *prop in schema.properties) {
  144. if (prop.optional && prop.array && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeLinkingObjects)) {
  145. // FIXME: message is awkward
  146. @throw RLMException(@"Property '%@.%@' cannot be made optional because optional '%@' properties are not supported.",
  147. className, prop.name, RLMTypeToString(prop.type));
  148. }
  149. }
  150. return schema;
  151. }
  152. + (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass {
  153. // For Swift classes we need an instance of the object when parsing properties
  154. id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil;
  155. if (NSArray<RLMProperty *> *props = [objectClass _getPropertiesWithInstance:swiftObjectInstance]) {
  156. return props;
  157. }
  158. NSArray *ignoredProperties = [objectClass ignoredProperties];
  159. NSDictionary *linkingObjectsProperties = [objectClass linkingObjectsProperties];
  160. NSDictionary *columnNameMap = [objectClass _realmColumnNames];
  161. unsigned int count;
  162. std::unique_ptr<objc_property_t[], decltype(&free)> props(class_copyPropertyList(objectClass, &count), &free);
  163. NSMutableArray<RLMProperty *> *propArray = [NSMutableArray arrayWithCapacity:count];
  164. NSSet *indexed = [[NSSet alloc] initWithArray:[objectClass indexedProperties]];
  165. for (unsigned int i = 0; i < count; i++) {
  166. NSString *propertyName = @(property_getName(props[i]));
  167. if ([ignoredProperties containsObject:propertyName]) {
  168. continue;
  169. }
  170. RLMProperty *prop = nil;
  171. if (isSwiftClass) {
  172. prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName
  173. indexed:[indexed containsObject:propertyName]
  174. linkPropertyDescriptor:linkingObjectsProperties[propertyName]
  175. property:props[i]
  176. instance:swiftObjectInstance];
  177. }
  178. else {
  179. prop = [[RLMProperty alloc] initWithName:propertyName
  180. indexed:[indexed containsObject:propertyName]
  181. linkPropertyDescriptor:linkingObjectsProperties[propertyName]
  182. property:props[i]];
  183. }
  184. if (prop) {
  185. if (columnNameMap) {
  186. prop.columnName = columnNameMap[prop.name];
  187. }
  188. [propArray addObject:prop];
  189. }
  190. }
  191. if (auto requiredProperties = [objectClass requiredProperties]) {
  192. for (RLMProperty *property in propArray) {
  193. bool required = [requiredProperties containsObject:property.name];
  194. if (required && property.type == RLMPropertyTypeObject && !property.array) {
  195. @throw RLMException(@"Object properties cannot be made required, "
  196. "but '+[%@ requiredProperties]' included '%@'", objectClass, property.name);
  197. }
  198. property.optional &= !required;
  199. }
  200. }
  201. for (RLMProperty *property in propArray) {
  202. if (!property.optional && property.type == RLMPropertyTypeObject && !property.array) {
  203. @throw RLMException(@"The `%@.%@` property must be marked as being optional.",
  204. [objectClass className], property.name);
  205. }
  206. }
  207. return propArray;
  208. }
  209. - (id)copyWithZone:(NSZone *)zone {
  210. RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init];
  211. schema->_objectClass = _objectClass;
  212. schema->_className = _className;
  213. schema->_objectClass = _objectClass;
  214. schema->_accessorClass = _objectClass;
  215. schema->_unmanagedClass = _unmanagedClass;
  216. schema->_isSwiftClass = _isSwiftClass;
  217. // call property setter to reset map and primary key
  218. schema.properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES];
  219. schema.computedProperties = [[NSArray allocWithZone:zone] initWithArray:_computedProperties copyItems:YES];
  220. return schema;
  221. }
  222. - (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema {
  223. if (objectSchema.properties.count != _properties.count) {
  224. return NO;
  225. }
  226. if (![_properties isEqualToArray:objectSchema.properties]) {
  227. return NO;
  228. }
  229. if (![_computedProperties isEqualToArray:objectSchema.computedProperties]) {
  230. return NO;
  231. }
  232. return YES;
  233. }
  234. - (NSString *)description {
  235. NSMutableString *propertiesString = [NSMutableString string];
  236. for (RLMProperty *property in self.properties) {
  237. [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
  238. }
  239. for (RLMProperty *property in self.computedProperties) {
  240. [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
  241. }
  242. return [NSString stringWithFormat:@"%@ {\n%@}", self.className, propertiesString];
  243. }
  244. - (NSString *)objectName {
  245. return [self.objectClass _realmObjectName] ?: _className;
  246. }
  247. - (realm::ObjectSchema)objectStoreCopy:(RLMSchema *)schema {
  248. ObjectSchema objectSchema;
  249. objectSchema.name = self.objectName.UTF8String;
  250. objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.columnName.UTF8String : "";
  251. for (RLMProperty *prop in _properties) {
  252. Property p = [prop objectStoreCopy:schema];
  253. p.is_primary = (prop == _primaryKeyProperty);
  254. objectSchema.persisted_properties.push_back(std::move(p));
  255. }
  256. for (RLMProperty *prop in _computedProperties) {
  257. objectSchema.computed_properties.push_back([prop objectStoreCopy:schema]);
  258. }
  259. return objectSchema;
  260. }
  261. + (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema {
  262. RLMObjectSchema *schema = [RLMObjectSchema new];
  263. schema.className = @(objectSchema.name.c_str());
  264. // create array of RLMProperties
  265. NSMutableArray *properties = [NSMutableArray arrayWithCapacity:objectSchema.persisted_properties.size()];
  266. for (const Property &prop : objectSchema.persisted_properties) {
  267. RLMProperty *property = [RLMProperty propertyForObjectStoreProperty:prop];
  268. property.isPrimary = (prop.name == objectSchema.primary_key);
  269. [properties addObject:property];
  270. }
  271. schema.properties = properties;
  272. NSMutableArray *computedProperties = [NSMutableArray arrayWithCapacity:objectSchema.computed_properties.size()];
  273. for (const Property &prop : objectSchema.computed_properties) {
  274. [computedProperties addObject:[RLMProperty propertyForObjectStoreProperty:prop]];
  275. }
  276. schema.computedProperties = computedProperties;
  277. // get primary key from realm metadata
  278. if (objectSchema.primary_key.length()) {
  279. NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()];
  280. schema.primaryKeyProperty = schema[primaryKeyString];
  281. if (!schema.primaryKeyProperty) {
  282. @throw RLMException(@"No property matching primary key '%@'", primaryKeyString);
  283. }
  284. }
  285. // for dynamic schema use vanilla RLMDynamicObject accessor classes
  286. schema.objectClass = RLMObject.class;
  287. schema.accessorClass = RLMDynamicObject.class;
  288. schema.unmanagedClass = RLMObject.class;
  289. return schema;
  290. }
  291. - (NSArray *)swiftGenericProperties {
  292. if (_swiftGenericProperties) {
  293. return _swiftGenericProperties;
  294. }
  295. // This check isn't semantically required, but avoiding accessing the local
  296. // static helps perf in the obj-c case
  297. if (!_isSwiftClass) {
  298. return _swiftGenericProperties = @[];
  299. }
  300. // Check if it's a swift class using the obj-c API
  301. static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
  302. if (![_accessorClass isSubclassOfClass:s_swiftObjectClass]) {
  303. return _swiftGenericProperties = @[];
  304. }
  305. NSMutableArray *genericProperties = [NSMutableArray new];
  306. for (RLMProperty *prop in _properties) {
  307. if (prop->_swiftIvar) {
  308. [genericProperties addObject:prop];
  309. }
  310. }
  311. // Currently all computed properties are Swift generics
  312. [genericProperties addObjectsFromArray:_computedProperties];
  313. return _swiftGenericProperties = genericProperties;
  314. }
  315. @end