You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
20 KiB
479 lines
20 KiB
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright 2014 Realm Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
#import "RLMObjectSchema_Private.hpp"
|
|
|
|
#import "RLMArray.h"
|
|
#import "RLMListBase.h"
|
|
#import "RLMObject_Private.h"
|
|
#import "RLMProperty_Private.hpp"
|
|
#import "RLMRealm_Dynamic.h"
|
|
#import "RLMRealm_Private.hpp"
|
|
#import "RLMSchema_Private.h"
|
|
#import "RLMSwiftSupport.h"
|
|
#import "RLMUtil.hpp"
|
|
|
|
#import "object_schema.hpp"
|
|
#import "object_store.hpp"
|
|
|
|
using namespace realm;
|
|
|
|
const Ivar RLMDummySwiftIvar = []() {
|
|
static int dummy;
|
|
return reinterpret_cast<objc_ivar *>(&dummy);
|
|
}();
|
|
|
|
// private properties
|
|
@interface RLMObjectSchema ()
|
|
@property (nonatomic, readwrite) NSDictionary<id, RLMProperty *> *allPropertiesByName;
|
|
@property (nonatomic, readwrite) NSString *className;
|
|
@end
|
|
|
|
@implementation RLMObjectSchema {
|
|
NSArray *_swiftGenericProperties;
|
|
}
|
|
|
|
- (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties {
|
|
self = [super init];
|
|
self.className = objectClassName;
|
|
self.properties = properties;
|
|
self.objectClass = objectClass;
|
|
self.accessorClass = objectClass;
|
|
self.unmanagedClass = objectClass;
|
|
return self;
|
|
}
|
|
|
|
// return properties by name
|
|
- (RLMProperty *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)key {
|
|
return _allPropertiesByName[key];
|
|
}
|
|
|
|
// create property map when setting property array
|
|
- (void)setProperties:(NSArray *)properties {
|
|
_properties = properties;
|
|
[self _propertiesDidChange];
|
|
}
|
|
|
|
- (void)setComputedProperties:(NSArray *)computedProperties {
|
|
_computedProperties = computedProperties;
|
|
[self _propertiesDidChange];
|
|
}
|
|
|
|
- (void)_propertiesDidChange {
|
|
NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count + _computedProperties.count];
|
|
NSUInteger index = 0;
|
|
for (RLMProperty *prop in _properties) {
|
|
prop.index = index++;
|
|
map[prop.name] = prop;
|
|
if (prop.isPrimary) {
|
|
self.primaryKeyProperty = prop;
|
|
}
|
|
}
|
|
index = 0;
|
|
for (RLMProperty *prop in _computedProperties) {
|
|
prop.index = index++;
|
|
map[prop.name] = prop;
|
|
}
|
|
_allPropertiesByName = map;
|
|
}
|
|
|
|
|
|
- (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty {
|
|
_primaryKeyProperty.isPrimary = NO;
|
|
primaryKeyProperty.isPrimary = YES;
|
|
_primaryKeyProperty = primaryKeyProperty;
|
|
}
|
|
|
|
+ (instancetype)schemaForObjectClass:(Class)objectClass {
|
|
RLMObjectSchema *schema = [RLMObjectSchema new];
|
|
|
|
// determine classname from objectclass as className method has not yet been updated
|
|
NSString *className = NSStringFromClass(objectClass);
|
|
bool hasSwiftName = [RLMSwiftSupport isSwiftClassName:className];
|
|
if (hasSwiftName) {
|
|
className = [RLMSwiftSupport demangleClassName:className];
|
|
}
|
|
|
|
static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
|
|
bool isSwift = hasSwiftName || [objectClass isSubclassOfClass:s_swiftObjectClass];
|
|
|
|
schema.className = className;
|
|
schema.objectClass = objectClass;
|
|
schema.accessorClass = objectClass;
|
|
schema.isSwiftClass = isSwift;
|
|
|
|
// create array of RLMProperties, inserting properties of superclasses first
|
|
Class cls = objectClass;
|
|
Class superClass = class_getSuperclass(cls);
|
|
NSArray *allProperties = @[];
|
|
while (superClass && superClass != RLMObjectBase.class) {
|
|
allProperties = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift]
|
|
arrayByAddingObjectsFromArray:allProperties];
|
|
cls = superClass;
|
|
superClass = class_getSuperclass(superClass);
|
|
}
|
|
NSArray *persistedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
|
|
return !RLMPropertyTypeIsComputed(property.type);
|
|
}]];
|
|
schema.properties = persistedProperties;
|
|
|
|
NSArray *computedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
|
|
return RLMPropertyTypeIsComputed(property.type);
|
|
}]];
|
|
schema.computedProperties = computedProperties;
|
|
|
|
// verify that we didn't add any properties twice due to inheritance
|
|
if (allProperties.count != [NSSet setWithArray:[allProperties valueForKey:@"name"]].count) {
|
|
NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[allProperties valueForKey:@"name"]];
|
|
NSArray *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) {
|
|
return [countedPropertyNames countForObject:object] > 1;
|
|
}]].allObjects;
|
|
|
|
if (duplicatePropertyNames.count == 1) {
|
|
@throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.firstObject, className);
|
|
} else {
|
|
@throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames componentsJoinedByString:@"', '"]);
|
|
}
|
|
}
|
|
|
|
if (NSString *primaryKey = [objectClass primaryKey]) {
|
|
for (RLMProperty *prop in schema.properties) {
|
|
if ([primaryKey isEqualToString:prop.name]) {
|
|
prop.indexed = YES;
|
|
schema.primaryKeyProperty = prop;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!schema.primaryKeyProperty) {
|
|
@throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className);
|
|
}
|
|
if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString) {
|
|
@throw RLMException(@"Property '%@' cannot be made the primary key of '%@' because it is not a 'string' or 'int' property.",
|
|
primaryKey, className);
|
|
}
|
|
}
|
|
|
|
for (RLMProperty *prop in schema.properties) {
|
|
if (prop.optional && prop.array && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeLinkingObjects)) {
|
|
// FIXME: message is awkward
|
|
@throw RLMException(@"Property '%@.%@' cannot be made optional because optional '%@' properties are not supported.",
|
|
className, prop.name, RLMTypeToString(prop.type));
|
|
}
|
|
}
|
|
|
|
return schema;
|
|
}
|
|
|
|
+ (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass {
|
|
Class objectUtil = [objectClass objectUtilClass:isSwiftClass];
|
|
NSArray *ignoredProperties = [objectUtil ignoredPropertiesForClass:objectClass];
|
|
NSDictionary *linkingObjectsProperties = [objectUtil linkingObjectsPropertiesForClass:objectClass];
|
|
NSDictionary *columnNameMap = [objectClass _realmColumnNames];
|
|
|
|
// For Swift classes we need an instance of the object when parsing properties
|
|
id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil;
|
|
|
|
unsigned int count;
|
|
std::unique_ptr<objc_property_t[], decltype(&free)> props(class_copyPropertyList(objectClass, &count), &free);
|
|
NSMutableArray<RLMProperty *> *propArray = [NSMutableArray arrayWithCapacity:count];
|
|
NSSet *indexed = [[NSSet alloc] initWithArray:[objectUtil indexedPropertiesForClass:objectClass]];
|
|
for (unsigned int i = 0; i < count; i++) {
|
|
NSString *propertyName = @(property_getName(props[i]));
|
|
if ([ignoredProperties containsObject:propertyName]) {
|
|
continue;
|
|
}
|
|
|
|
RLMProperty *prop = nil;
|
|
if (isSwiftClass) {
|
|
prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName
|
|
indexed:[indexed containsObject:propertyName]
|
|
linkPropertyDescriptor:linkingObjectsProperties[propertyName]
|
|
property:props[i]
|
|
instance:swiftObjectInstance];
|
|
}
|
|
else {
|
|
prop = [[RLMProperty alloc] initWithName:propertyName
|
|
indexed:[indexed containsObject:propertyName]
|
|
linkPropertyDescriptor:linkingObjectsProperties[propertyName]
|
|
property:props[i]];
|
|
}
|
|
|
|
if (prop) {
|
|
if (columnNameMap) {
|
|
prop.columnName = columnNameMap[prop.name];
|
|
}
|
|
[propArray addObject:prop];
|
|
}
|
|
}
|
|
|
|
if (isSwiftClass) {
|
|
[self addSwiftProperties:propArray objectUtil:objectUtil instance:swiftObjectInstance
|
|
indexed:indexed nameMap:columnNameMap];
|
|
}
|
|
|
|
if (auto requiredProperties = [objectUtil requiredPropertiesForClass:objectClass]) {
|
|
for (RLMProperty *property in propArray) {
|
|
bool required = [requiredProperties containsObject:property.name];
|
|
if (required && property.type == RLMPropertyTypeObject && !property.array) {
|
|
@throw RLMException(@"Object properties cannot be made required, "
|
|
"but '+[%@ requiredProperties]' included '%@'", objectClass, property.name);
|
|
}
|
|
property.optional &= !required;
|
|
}
|
|
}
|
|
|
|
for (RLMProperty *property in propArray) {
|
|
if (!property.optional && property.type == RLMPropertyTypeObject && !property.array) {
|
|
@throw RLMException(@"The `%@.%@` property must be marked as being optional.",
|
|
[objectClass className], property.name);
|
|
}
|
|
}
|
|
|
|
return propArray;
|
|
}
|
|
|
|
+ (void)addSwiftProperties:(NSMutableArray<RLMProperty *> *)propArray
|
|
objectUtil:(Class)objectUtil
|
|
instance:(id)instance
|
|
indexed:(NSSet<NSString *> *)indexed
|
|
nameMap:(NSDictionary<NSString *, NSString *> *)columnNameMap {
|
|
// The property list reported to the obj-c runtime for Swift objects is
|
|
// incomplete and doesn't include Swift generics like List<> and
|
|
// RealmOptional<>, and is missing information for some properties that
|
|
// are reported, such as the difference between `String` and `String?`. To
|
|
// deal with this, we also get the properties from Swift reflection, and
|
|
// merge the results.
|
|
|
|
NSArray<RLMSwiftPropertyMetadata *> *props = [objectUtil getSwiftProperties:instance];
|
|
if (!props) {
|
|
// A Swift subclass of RLMObject, which operates under obj-c rules
|
|
return;
|
|
}
|
|
|
|
// Track the index that we expect the next property to go in, for inserting
|
|
// generic properties into the correct place
|
|
NSUInteger nextIndex = 0;
|
|
for (RLMSwiftPropertyMetadata *md in props) {
|
|
// In theory existing should only ever be nextIndex or NSNotFound, and
|
|
// this search is just a waste of time.
|
|
// FIXME: verify if this is actually true
|
|
NSUInteger existing = [propArray indexOfObjectPassingTest:^(RLMProperty *obj, NSUInteger, BOOL *) {
|
|
return [obj.name isEqualToString:md.propertyName];
|
|
}];
|
|
|
|
RLMProperty *prop;
|
|
switch (md.kind) {
|
|
case RLMSwiftPropertyKindList: // List<>
|
|
prop = [[RLMProperty alloc] initSwiftListPropertyWithName:md.propertyName instance:instance];
|
|
break;
|
|
case RLMSwiftPropertyKindLinkingObjects: { // LinkingObjects<>
|
|
Ivar ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
|
|
prop = [[RLMProperty alloc] initSwiftLinkingObjectsPropertyWithName:md.propertyName
|
|
ivar:ivar
|
|
objectClassName:md.className
|
|
linkOriginPropertyName:md.linkedPropertyName];
|
|
break;
|
|
}
|
|
case RLMSwiftPropertyKindOptional: {
|
|
if (existing != NSNotFound) {
|
|
// String?, Data?, Date? with a non-nil default value
|
|
// We already know about this property from obj-c and we
|
|
// defaulted to optional, so nothing to do
|
|
break;
|
|
}
|
|
|
|
Ivar ivar;
|
|
if (md.propertyType == RLMPropertyTypeString) {
|
|
// FIXME: A non-@objc dynamic String? property which we
|
|
// can't actually read so we're always just going to pretend it's nil
|
|
// https://github.com/realm/realm-cocoa/issues/5784
|
|
ivar = RLMDummySwiftIvar;
|
|
}
|
|
else {
|
|
// RealmOptional<>
|
|
ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
|
|
}
|
|
|
|
prop = [[RLMProperty alloc] initSwiftOptionalPropertyWithName:md.propertyName
|
|
indexed:[indexed containsObject:md.propertyName]
|
|
ivar:ivar
|
|
propertyType:md.propertyType];
|
|
break;
|
|
}
|
|
|
|
case RLMSwiftPropertyKindOther:
|
|
case RLMSwiftPropertyKindNilLiteralOptional:
|
|
// This might be a property which wasn't reported to obj-c and
|
|
// isn't one of our supported generic types, in which case we
|
|
// ignore it
|
|
if (existing == NSNotFound) {
|
|
--nextIndex;
|
|
}
|
|
// or it might be a String?, Data?, Date? or object field with
|
|
// a nil default value
|
|
else if (md.kind == RLMSwiftPropertyKindNilLiteralOptional) {
|
|
propArray[existing].optional = true;
|
|
}
|
|
// or it may be some non-optional property which may have been
|
|
// previously marked as optional due to that being the default
|
|
// in obj-c
|
|
else {
|
|
propArray[existing].optional = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (prop) {
|
|
if (columnNameMap) {
|
|
prop.columnName = columnNameMap[prop.name];
|
|
}
|
|
[propArray insertObject:prop atIndex:nextIndex];
|
|
}
|
|
|
|
++nextIndex;
|
|
}
|
|
}
|
|
|
|
- (id)copyWithZone:(NSZone *)zone {
|
|
RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init];
|
|
schema->_objectClass = _objectClass;
|
|
schema->_className = _className;
|
|
schema->_objectClass = _objectClass;
|
|
schema->_accessorClass = _objectClass;
|
|
schema->_unmanagedClass = _unmanagedClass;
|
|
schema->_isSwiftClass = _isSwiftClass;
|
|
|
|
// call property setter to reset map and primary key
|
|
schema.properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES];
|
|
schema.computedProperties = [[NSArray allocWithZone:zone] initWithArray:_computedProperties copyItems:YES];
|
|
|
|
return schema;
|
|
}
|
|
|
|
- (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema {
|
|
if (objectSchema.properties.count != _properties.count) {
|
|
return NO;
|
|
}
|
|
|
|
if (![_properties isEqualToArray:objectSchema.properties]) {
|
|
return NO;
|
|
}
|
|
if (![_computedProperties isEqualToArray:objectSchema.computedProperties]) {
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (NSString *)description {
|
|
NSMutableString *propertiesString = [NSMutableString string];
|
|
for (RLMProperty *property in self.properties) {
|
|
[propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
|
|
}
|
|
for (RLMProperty *property in self.computedProperties) {
|
|
[propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
|
|
}
|
|
return [NSString stringWithFormat:@"%@ {\n%@}", self.className, propertiesString];
|
|
}
|
|
|
|
- (NSString *)objectName {
|
|
return [self.objectClass _realmObjectName] ?: _className;
|
|
}
|
|
|
|
- (realm::ObjectSchema)objectStoreCopy:(RLMSchema *)schema {
|
|
ObjectSchema objectSchema;
|
|
objectSchema.name = self.objectName.UTF8String;
|
|
objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.columnName.UTF8String : "";
|
|
for (RLMProperty *prop in _properties) {
|
|
Property p = [prop objectStoreCopy:schema];
|
|
p.is_primary = (prop == _primaryKeyProperty);
|
|
objectSchema.persisted_properties.push_back(std::move(p));
|
|
}
|
|
for (RLMProperty *prop in _computedProperties) {
|
|
objectSchema.computed_properties.push_back([prop objectStoreCopy:schema]);
|
|
}
|
|
return objectSchema;
|
|
}
|
|
|
|
+ (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema {
|
|
RLMObjectSchema *schema = [RLMObjectSchema new];
|
|
schema.className = @(objectSchema.name.c_str());
|
|
|
|
// create array of RLMProperties
|
|
NSMutableArray *properties = [NSMutableArray arrayWithCapacity:objectSchema.persisted_properties.size()];
|
|
for (const Property &prop : objectSchema.persisted_properties) {
|
|
RLMProperty *property = [RLMProperty propertyForObjectStoreProperty:prop];
|
|
property.isPrimary = (prop.name == objectSchema.primary_key);
|
|
[properties addObject:property];
|
|
}
|
|
schema.properties = properties;
|
|
|
|
NSMutableArray *computedProperties = [NSMutableArray arrayWithCapacity:objectSchema.computed_properties.size()];
|
|
for (const Property &prop : objectSchema.computed_properties) {
|
|
[computedProperties addObject:[RLMProperty propertyForObjectStoreProperty:prop]];
|
|
}
|
|
schema.computedProperties = computedProperties;
|
|
|
|
// get primary key from realm metadata
|
|
if (objectSchema.primary_key.length()) {
|
|
NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()];
|
|
schema.primaryKeyProperty = schema[primaryKeyString];
|
|
if (!schema.primaryKeyProperty) {
|
|
@throw RLMException(@"No property matching primary key '%@'", primaryKeyString);
|
|
}
|
|
}
|
|
|
|
// for dynamic schema use vanilla RLMDynamicObject accessor classes
|
|
schema.objectClass = RLMObject.class;
|
|
schema.accessorClass = RLMDynamicObject.class;
|
|
schema.unmanagedClass = RLMObject.class;
|
|
|
|
return schema;
|
|
}
|
|
|
|
- (NSArray *)swiftGenericProperties {
|
|
if (_swiftGenericProperties) {
|
|
return _swiftGenericProperties;
|
|
}
|
|
|
|
// This check isn't semantically required, but avoiding accessing the local
|
|
// static helps perf in the obj-c case
|
|
if (!_isSwiftClass) {
|
|
return _swiftGenericProperties = @[];
|
|
}
|
|
|
|
// Check if it's a swift class using the obj-c API
|
|
static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
|
|
if (![_accessorClass isSubclassOfClass:s_swiftObjectClass]) {
|
|
return _swiftGenericProperties = @[];
|
|
}
|
|
|
|
NSMutableArray *genericProperties = [NSMutableArray new];
|
|
for (RLMProperty *prop in _properties) {
|
|
if (prop->_swiftIvar) {
|
|
[genericProperties addObject:prop];
|
|
}
|
|
}
|
|
// Currently all computed properties are Swift generics
|
|
[genericProperties addObjectsFromArray:_computedProperties];
|
|
|
|
return _swiftGenericProperties = genericProperties;
|
|
}
|
|
|
|
@end
|
|
|