RLMObjectSchema.mm 16 KB

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