1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2014 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
19 #import "RLMObjectSchema_Private.hpp"
22 #import "RLMListBase.h"
23 #import "RLMObject_Private.h"
24 #import "RLMProperty_Private.hpp"
25 #import "RLMRealm_Dynamic.h"
26 #import "RLMRealm_Private.hpp"
27 #import "RLMSchema_Private.h"
28 #import "RLMSwiftSupport.h"
31 #import "object_store.hpp"
33 using namespace realm;
36 @interface RLMObjectSchema ()
37 @property (nonatomic, readwrite) NSDictionary<id, RLMProperty *> *allPropertiesByName;
38 @property (nonatomic, readwrite) NSString *className;
41 @implementation RLMObjectSchema {
42 NSArray *_swiftGenericProperties;
45 - (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties {
47 self.className = objectClassName;
48 self.properties = properties;
49 self.objectClass = objectClass;
50 self.accessorClass = objectClass;
51 self.unmanagedClass = objectClass;
55 // return properties by name
56 - (RLMProperty *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)key {
57 return _allPropertiesByName[key];
60 // create property map when setting property array
61 - (void)setProperties:(NSArray *)properties {
62 _properties = properties;
63 [self _propertiesDidChange];
66 - (void)setComputedProperties:(NSArray *)computedProperties {
67 _computedProperties = computedProperties;
68 [self _propertiesDidChange];
71 - (void)_propertiesDidChange {
72 NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count + _computedProperties.count];
74 for (RLMProperty *prop in _properties) {
76 map[prop.name] = prop;
78 self.primaryKeyProperty = prop;
81 for (RLMProperty *prop in _computedProperties) {
82 map[prop.name] = prop;
84 _allPropertiesByName = map;
88 - (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty {
89 _primaryKeyProperty.isPrimary = NO;
90 primaryKeyProperty.isPrimary = YES;
91 _primaryKeyProperty = primaryKeyProperty;
94 + (instancetype)schemaForObjectClass:(Class)objectClass {
95 RLMObjectSchema *schema = [RLMObjectSchema new];
97 // determine classname from objectclass as className method has not yet been updated
98 NSString *className = NSStringFromClass(objectClass);
99 bool hasSwiftName = [RLMSwiftSupport isSwiftClassName:className];
101 className = [RLMSwiftSupport demangleClassName:className];
104 static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
105 bool isSwift = hasSwiftName || [objectClass isSubclassOfClass:s_swiftObjectClass];
107 schema.className = className;
108 schema.objectClass = objectClass;
109 schema.accessorClass = objectClass;
110 schema.isSwiftClass = isSwift;
112 // create array of RLMProperties, inserting properties of superclasses first
113 Class cls = objectClass;
114 Class superClass = class_getSuperclass(cls);
115 NSArray *allProperties = @[];
116 while (superClass && superClass != RLMObjectBase.class) {
117 allProperties = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift]
118 arrayByAddingObjectsFromArray:allProperties];
120 superClass = class_getSuperclass(superClass);
122 NSArray *persistedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
123 return !RLMPropertyTypeIsComputed(property.type);
125 schema.properties = persistedProperties;
127 NSArray *computedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
128 return RLMPropertyTypeIsComputed(property.type);
130 schema.computedProperties = computedProperties;
132 // verify that we didn't add any properties twice due to inheritance
133 if (allProperties.count != [NSSet setWithArray:[allProperties valueForKey:@"name"]].count) {
134 NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[allProperties valueForKey:@"name"]];
135 NSArray *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) {
136 return [countedPropertyNames countForObject:object] > 1;
139 if (duplicatePropertyNames.count == 1) {
140 @throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.firstObject, className);
142 @throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames componentsJoinedByString:@"', '"]);
146 if (NSString *primaryKey = [objectClass primaryKey]) {
147 for (RLMProperty *prop in schema.properties) {
148 if ([primaryKey isEqualToString:prop.name]) {
150 schema.primaryKeyProperty = prop;
155 if (!schema.primaryKeyProperty) {
156 @throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className);
158 if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString) {
159 @throw RLMException(@"Property '%@' cannot be made the primary key of '%@' because it is not a 'string' or 'int' property.",
160 primaryKey, className);
164 for (RLMProperty *prop in schema.properties) {
165 if (prop.optional && prop.array && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeLinkingObjects)) {
166 // FIXME: message is awkward
167 @throw RLMException(@"Property '%@.%@' cannot be made optional because optional '%@' properties are not supported.",
168 className, prop.name, RLMTypeToString(prop.type));
175 + (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass {
176 Class objectUtil = [objectClass objectUtilClass:isSwiftClass];
177 NSArray *ignoredProperties = [objectUtil ignoredPropertiesForClass:objectClass];
178 NSDictionary *linkingObjectsProperties = [objectUtil linkingObjectsPropertiesForClass:objectClass];
180 // For Swift classes we need an instance of the object when parsing properties
181 id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil;
184 std::unique_ptr<objc_property_t[], decltype(&free)> props(class_copyPropertyList(objectClass, &count), &free);
185 NSMutableArray<RLMProperty *> *propArray = [NSMutableArray arrayWithCapacity:count];
186 NSSet *indexed = [[NSSet alloc] initWithArray:[objectUtil indexedPropertiesForClass:objectClass]];
187 for (unsigned int i = 0; i < count; i++) {
188 NSString *propertyName = @(property_getName(props[i]));
189 if ([ignoredProperties containsObject:propertyName]) {
193 RLMProperty *prop = nil;
195 prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName
196 indexed:[indexed containsObject:propertyName]
197 linkPropertyDescriptor:linkingObjectsProperties[propertyName]
199 instance:swiftObjectInstance];
202 prop = [[RLMProperty alloc] initWithName:propertyName
203 indexed:[indexed containsObject:propertyName]
204 linkPropertyDescriptor:linkingObjectsProperties[propertyName]
209 [propArray addObject:prop];
214 [self addSwiftProperties:propArray objectUtil:objectUtil instance:swiftObjectInstance indexed:indexed];
217 if (auto requiredProperties = [objectUtil requiredPropertiesForClass:objectClass]) {
218 for (RLMProperty *property in propArray) {
219 bool required = [requiredProperties containsObject:property.name];
220 if (required && property.type == RLMPropertyTypeObject && !property.array) {
221 @throw RLMException(@"Object properties cannot be made required, "
222 "but '+[%@ requiredProperties]' included '%@'", objectClass, property.name);
224 property.optional &= !required;
228 for (RLMProperty *property in propArray) {
229 if (!property.optional && property.type == RLMPropertyTypeObject && !property.array) {
230 @throw RLMException(@"The `%@.%@` property must be marked as being optional.",
231 [objectClass className], property.name);
238 + (void)addSwiftProperties:(NSMutableArray<RLMProperty *> *)propArray
239 objectUtil:(Class)objectUtil
240 instance:(id)instance
241 indexed:(NSSet<NSString *> *)indexed {
242 // The property list reported to the obj-c runtime for Swift objects is
243 // incomplete and doesn't include Swift generics like List<> and
244 // RealmOptional<>, and is missing information for some properties that
245 // are reported, such as the difference between `String` and `String?`. To
246 // deal with this, we also get the properties from Swift reflection, and
247 // merge the results.
249 NSArray<RLMSwiftPropertyMetadata *> *props = [objectUtil getSwiftProperties:instance];
251 // A Swift subclass of RLMObject, which operates under obj-c rules
255 // Track the index that we expect the next property to go in, for inserting
256 // generic properties into the correct place
257 NSUInteger nextIndex = 0;
258 for (RLMSwiftPropertyMetadata *md in props) {
259 // In theory existing should only ever be nextIndex or NSNotFound, and
260 // this search is just a waste of time.
261 // FIXME: verify if this is actually true
262 NSUInteger existing = [propArray indexOfObjectPassingTest:^(RLMProperty *obj, NSUInteger, BOOL *) {
263 return [obj.name isEqualToString:md.propertyName];
267 case RLMSwiftPropertyKindList: // List<>
268 [propArray insertObject:[[RLMProperty alloc] initSwiftListPropertyWithName:md.propertyName
272 case RLMSwiftPropertyKindLinkingObjects: { // LinkingObjects<>
273 Ivar ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
274 [propArray insertObject:[[RLMProperty alloc] initSwiftLinkingObjectsPropertyWithName:md.propertyName
276 objectClassName:md.className
277 linkOriginPropertyName:md.linkedPropertyName]
281 case RLMSwiftPropertyKindOptional: {
282 if (existing != NSNotFound) {
283 // String?, Data?, Date? with a non-nil default value
284 // We already know about this property from obj-c and we
285 // defaulted to optional, so nothing to do
290 Ivar ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
291 BOOL isIndexed = [indexed containsObject:md.propertyName];
292 [propArray insertObject:[[RLMProperty alloc] initSwiftOptionalPropertyWithName:md.propertyName
295 propertyType:md.propertyType]
300 case RLMSwiftPropertyKindOther:
301 case RLMSwiftPropertyKindNilLiteralOptional:
302 // This might be a property which wasn't reported to obj-c and
303 // isn't one of our supported generic types, in which case we
305 if (existing == NSNotFound) {
308 // or it might be a String?, Data?, Date? or object field with
309 // a nil default value
310 else if (md.kind == RLMSwiftPropertyKindNilLiteralOptional) {
311 propArray[existing].optional = true;
313 // or it may be some non-optional property which may have been
314 // previously marked as optional due to that being the default
317 propArray[existing].optional = false;
326 - (id)copyWithZone:(NSZone *)zone {
327 RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init];
328 schema->_objectClass = _objectClass;
329 schema->_className = _className;
330 schema->_objectClass = _objectClass;
331 schema->_accessorClass = _objectClass;
332 schema->_unmanagedClass = _unmanagedClass;
333 schema->_isSwiftClass = _isSwiftClass;
335 // call property setter to reset map and primary key
336 schema.properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES];
337 schema.computedProperties = [[NSArray allocWithZone:zone] initWithArray:_computedProperties copyItems:YES];
342 - (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema {
343 if (objectSchema.properties.count != _properties.count) {
347 if (![_properties isEqualToArray:objectSchema.properties]) {
350 if (![_computedProperties isEqualToArray:objectSchema.computedProperties]) {
357 - (NSString *)description {
358 NSMutableString *propertiesString = [NSMutableString string];
359 for (RLMProperty *property in self.properties) {
360 [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
362 for (RLMProperty *property in self.computedProperties) {
363 [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
365 return [NSString stringWithFormat:@"%@ {\n%@}", self.className, propertiesString];
368 - (NSString *)objectName {
369 return [self.objectClass _realmObjectName] ?: _className;
372 - (realm::ObjectSchema)objectStoreCopy {
373 ObjectSchema objectSchema;
374 objectSchema.name = self.objectName.UTF8String;
375 objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.name.UTF8String : "";
376 for (RLMProperty *prop in _properties) {
377 Property p = [prop objectStoreCopy];
378 p.is_primary = (prop == _primaryKeyProperty);
379 objectSchema.persisted_properties.push_back(std::move(p));
381 for (RLMProperty *prop in _computedProperties) {
382 objectSchema.computed_properties.push_back([prop objectStoreCopy]);
387 + (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema {
388 RLMObjectSchema *schema = [RLMObjectSchema new];
389 schema.className = @(objectSchema.name.c_str());
391 // create array of RLMProperties
392 NSMutableArray *properties = [NSMutableArray arrayWithCapacity:objectSchema.persisted_properties.size()];
393 for (const Property &prop : objectSchema.persisted_properties) {
394 RLMProperty *property = [RLMProperty propertyForObjectStoreProperty:prop];
395 property.isPrimary = (prop.name == objectSchema.primary_key);
396 [properties addObject:property];
398 schema.properties = properties;
400 NSMutableArray *computedProperties = [NSMutableArray arrayWithCapacity:objectSchema.computed_properties.size()];
401 for (const Property &prop : objectSchema.computed_properties) {
402 [computedProperties addObject:[RLMProperty propertyForObjectStoreProperty:prop]];
404 schema.computedProperties = computedProperties;
406 // get primary key from realm metadata
407 if (objectSchema.primary_key.length()) {
408 NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()];
409 schema.primaryKeyProperty = schema[primaryKeyString];
410 if (!schema.primaryKeyProperty) {
411 @throw RLMException(@"No property matching primary key '%@'", primaryKeyString);
415 // for dynamic schema use vanilla RLMDynamicObject accessor classes
416 schema.objectClass = RLMObject.class;
417 schema.accessorClass = RLMDynamicObject.class;
418 schema.unmanagedClass = RLMObject.class;
423 - (NSArray *)swiftGenericProperties {
424 if (_swiftGenericProperties) {
425 return _swiftGenericProperties;
428 // This check isn't semantically required, but avoiding accessing the local
429 // static helps perf in the obj-c case
430 if (!_isSwiftClass) {
431 return _swiftGenericProperties = @[];
434 // Check if it's a swift class using the obj-c API
435 static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
436 if (![_accessorClass isSubclassOfClass:s_swiftObjectClass]) {
437 return _swiftGenericProperties = @[];
440 NSMutableArray *genericProperties = [NSMutableArray new];
441 for (RLMProperty *prop in _properties) {
442 if (prop->_swiftIvar) {
443 [genericProperties addObject:prop];
446 // Currently all computed properties are Swift generics
447 [genericProperties addObjectsFromArray:_computedProperties];
449 return _swiftGenericProperties = genericProperties;