//////////////////////////////////////////////////////////////////////////// // // 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 "RLMProperty_Private.hpp" #import "RLMArray_Private.hpp" #import "RLMDictionary_Private.hpp" #import "RLMObject.h" #import "RLMObjectSchema_Private.hpp" #import "RLMObject_Private.h" #import "RLMSchema_Private.h" #import "RLMSet_Private.hpp" #import "RLMSwiftSupport.h" #import "RLMUtil.hpp" #import <realm/object-store/property.hpp> static_assert((int)RLMPropertyTypeInt == (int)realm::PropertyType::Int); static_assert((int)RLMPropertyTypeBool == (int)realm::PropertyType::Bool); static_assert((int)RLMPropertyTypeFloat == (int)realm::PropertyType::Float); static_assert((int)RLMPropertyTypeDouble == (int)realm::PropertyType::Double); static_assert((int)RLMPropertyTypeString == (int)realm::PropertyType::String); static_assert((int)RLMPropertyTypeData == (int)realm::PropertyType::Data); static_assert((int)RLMPropertyTypeDate == (int)realm::PropertyType::Date); static_assert((int)RLMPropertyTypeObject == (int)realm::PropertyType::Object); static_assert((int)RLMPropertyTypeObjectId == (int)realm::PropertyType::ObjectId); static_assert((int)RLMPropertyTypeDecimal128 == (int)realm::PropertyType::Decimal); static_assert((int)RLMPropertyTypeUUID == (int)realm::PropertyType::UUID); static_assert((int)RLMPropertyTypeAny == (int)realm::PropertyType::Mixed); BOOL RLMPropertyTypeIsComputed(RLMPropertyType propertyType) { return propertyType == RLMPropertyTypeLinkingObjects; } // Swift obeys the ARC naming conventions for method families (except for init) // but the end result doesn't really work (using KVC on a method returning a // retained value results in a leak, but not returning a retained value results // in crashes). Objective-C makes properties with naming fitting the method // families a compile error, so we just disallow them in Swift as well. // http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families void RLMValidateSwiftPropertyName(NSString *name) { // To belong to a method family, the property name must begin with the family // name followed by a non-lowercase letter (or nothing), with an optional // leading underscore const char *str = name.UTF8String; if (str[0] == '_') ++str; auto nameSize = strlen(str); // Note that "init" is deliberately not in this list because Swift does not // infer family membership for it. for (auto family : {"alloc", "new", "copy", "mutableCopy"}) { auto familySize = strlen(family); if (nameSize < familySize || !std::equal(str, str + familySize, family)) { continue; } if (familySize == nameSize || !islower(str[familySize])) { @throw RLMException(@"Property names beginning with '%s' are not " "supported. Swift follows ARC's ownership " "rules for methods based on their name, which " "results in memory leaks when accessing " "properties which return retained values via KVC.", family); } return; } } static bool rawTypeShouldBeTreatedAsComputedProperty(NSString *rawType) { return [rawType isEqualToString:@"@\"RLMLinkingObjects\""] || [rawType hasPrefix:@"@\"RLMLinkingObjects<"]; } @implementation RLMProperty + (instancetype)propertyForObjectStoreProperty:(const realm::Property &)prop { auto ret = [[RLMProperty alloc] initWithName:@(prop.name.c_str()) type:static_cast<RLMPropertyType>(prop.type & ~realm::PropertyType::Flags) objectClassName:prop.object_type.length() ? @(prop.object_type.c_str()) : nil linkOriginPropertyName:prop.link_origin_property_name.length() ? @(prop.link_origin_property_name.c_str()) : nil indexed:prop.is_indexed optional:isNullable(prop.type)]; if (is_array(prop.type)) { ret->_array = true; } if (is_set(prop.type)) { ret->_set = true; } if (is_dictionary(prop.type)) { // TODO: We need a way to store the dictionary // key type in realm::Property once we support more // key types. ret->_dictionaryKeyType = RLMPropertyTypeString; ret->_dictionary = true; } if (!prop.public_name.empty()) { ret->_columnName = ret->_name; ret->_name = @(prop.public_name.c_str()); } return ret; } - (instancetype)initWithName:(NSString *)name type:(RLMPropertyType)type objectClassName:(NSString *)objectClassName linkOriginPropertyName:(NSString *)linkOriginPropertyName indexed:(BOOL)indexed optional:(BOOL)optional { self = [super init]; if (self) { _name = name; _type = type; _objectClassName = objectClassName; _linkOriginPropertyName = linkOriginPropertyName; _indexed = indexed; _optional = optional; [self updateAccessors]; } return self; } - (void)updateAccessors { // populate getter/setter names if generic if (!_getterName) { _getterName = _name; } if (!_setterName) { // Objective-C setters only capitalize the first letter of the property name if it falls between 'a' and 'z' int asciiCode = [_name characterAtIndex:0]; BOOL shouldUppercase = asciiCode >= 'a' && asciiCode <= 'z'; NSString *firstChar = [_name substringToIndex:1]; firstChar = shouldUppercase ? firstChar.uppercaseString : firstChar; _setterName = [NSString stringWithFormat:@"set%@%@:", firstChar, [_name substringFromIndex:1]]; } _getterSel = NSSelectorFromString(_getterName); _setterSel = NSSelectorFromString(_setterName); } static realm::util::Optional<RLMPropertyType> typeFromProtocolString(const char *type) { if (strcmp(type, "RLMValue>\"") == 0) { return RLMPropertyTypeAny; } if (strncmp(type, "RLM", 3)) { return realm::none; } type += 3; if (strcmp(type, "Int>\"") == 0) { return RLMPropertyTypeInt; } if (strcmp(type, "Float>\"") == 0) { return RLMPropertyTypeFloat; } if (strcmp(type, "Double>\"") == 0) { return RLMPropertyTypeDouble; } if (strcmp(type, "Bool>\"") == 0) { return RLMPropertyTypeBool; } if (strcmp(type, "String>\"") == 0) { return RLMPropertyTypeString; } if (strcmp(type, "Data>\"") == 0) { return RLMPropertyTypeData; } if (strcmp(type, "Date>\"") == 0) { return RLMPropertyTypeDate; } if (strcmp(type, "Decimal128>\"") == 0) { return RLMPropertyTypeDecimal128; } if (strcmp(type, "ObjectId>\"") == 0) { return RLMPropertyTypeObjectId; } if (strcmp(type, "UUID>\"") == 0) { return RLMPropertyTypeUUID; } return realm::none; } // determine RLMPropertyType from objc code - returns true if valid type was found/set - (BOOL)setTypeFromRawType:(NSString *)rawType { const char *code = rawType.UTF8String; switch (*code) { case 's': // short case 'i': // int case 'l': // long case 'q': // long long _type = RLMPropertyTypeInt; return YES; case 'f': _type = RLMPropertyTypeFloat; return YES; case 'd': _type = RLMPropertyTypeDouble; return YES; case 'c': // BOOL is stored as char - since rlm has no char type this is ok case 'B': _type = RLMPropertyTypeBool; return YES; case '@': break; default: return NO; } _optional = true; static const char arrayPrefix[] = "@\"RLMArray<"; static const int arrayPrefixLen = sizeof(arrayPrefix) - 1; static const char setPrefix[] = "@\"RLMSet<"; static const int setPrefixLen = sizeof(setPrefix) - 1; static const char dictionaryPrefix[] = "@\"RLMDictionary<"; static const int dictionaryPrefixLen = sizeof(dictionaryPrefix) - 1; static const char numberPrefix[] = "@\"NSNumber<"; static const int numberPrefixLen = sizeof(numberPrefix) - 1; static const char linkingObjectsPrefix[] = "@\"RLMLinkingObjects"; static const int linkingObjectsPrefixLen = sizeof(linkingObjectsPrefix) - 1; _array = strncmp(code, arrayPrefix, arrayPrefixLen) == 0; _set = strncmp(code, setPrefix, setPrefixLen) == 0; _dictionary = strncmp(code, dictionaryPrefix, dictionaryPrefixLen) == 0; if (strcmp(code, "@\"NSString\"") == 0) { _type = RLMPropertyTypeString; } else if (strcmp(code, "@\"NSDate\"") == 0) { _type = RLMPropertyTypeDate; } else if (strcmp(code, "@\"NSData\"") == 0) { _type = RLMPropertyTypeData; } else if (strcmp(code, "@\"RLMDecimal128\"") == 0) { _type = RLMPropertyTypeDecimal128; } else if (strcmp(code, "@\"RLMObjectId\"") == 0) { _type = RLMPropertyTypeObjectId; } else if (strcmp(code, "@\"NSUUID\"") == 0) { _type = RLMPropertyTypeUUID; } else if (strcmp(code, "@\"<RLMValue>\"") == 0) { _type = RLMPropertyTypeAny; // Mixed can represent a null type but can't explicitly be an optional type. _optional = false; } else if (_array || _set || _dictionary) { size_t prefixLen = 0; NSString *collectionName; if (_array) { prefixLen = arrayPrefixLen; collectionName = @"RLMArray"; } else if (_set) { prefixLen = setPrefixLen; collectionName = @"RLMSet"; } else if (_dictionary) { // get the type, by working backward from RLMDictionary<Key, Type> size_t typeLen = 0; size_t codeSize = strlen(code); for (size_t i = codeSize; i > 0; i--) { if (code[i] == '>' && i != (codeSize-2)) { // -2 means we skip the first time we see '>' typeLen = i; break; } } prefixLen = typeLen+size_t(2); // +2 start at the type name collectionName = @"RLMDictionary"; // Get the key type if (strstr(code + dictionaryPrefixLen, "RLMString><") != NULL) { _dictionaryKeyType = RLMPropertyTypeString; } } if (auto type = typeFromProtocolString(code + prefixLen)) { _type = *type; return YES; } // get object class from type string - @"RLMSomeCollection<objectClassName>" _objectClassName = [[NSString alloc] initWithBytes:code + prefixLen length:strlen(code + prefixLen) - 2 // drop trailing >" encoding:NSUTF8StringEncoding]; if ([RLMSchema classForString:_objectClassName]) { // Dictionaries require object types to be nullable. This is due to // the fact that if you delete a realm object that exists in a dictionary // the key should stay present but the value should be null. _optional = _dictionary; _type = RLMPropertyTypeObject; return YES; } @throw RLMException(@"Property '%@' is of type '%@<%@>' which is not a supported %@ object type. " @"%@ can only contain instances of RLMObject subclasses. " @"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, collectionName, _objectClassName, collectionName, collectionName); } else if (strncmp(code, numberPrefix, numberPrefixLen) == 0) { auto type = typeFromProtocolString(code + numberPrefixLen); if (type && (*type == RLMPropertyTypeInt || *type == RLMPropertyTypeFloat || *type == RLMPropertyTypeDouble || *type == RLMPropertyTypeBool)) { _type = *type; return YES; } @throw RLMException(@"Property '%@' is of type %s which is not a supported NSNumber object type. " @"NSNumbers can only be RLMInt, RLMFloat, RLMDouble, and RLMBool at the moment. " @"See https://realm.io/docs/objc/latest for more information.", _name, code + 1); } else if (strncmp(code, linkingObjectsPrefix, linkingObjectsPrefixLen) == 0 && (code[linkingObjectsPrefixLen] == '"' || code[linkingObjectsPrefixLen] == '<')) { _type = RLMPropertyTypeLinkingObjects; _optional = false; _array = true; if (!_objectClassName || !_linkOriginPropertyName) { @throw RLMException(@"Property '%@' is of type RLMLinkingObjects but +linkingObjectsProperties did not specify the class " "or property that is the origin of the link.", _name); } // If the property was declared with a protocol indicating the contained type, validate that it matches // the class from the dictionary returned by +linkingObjectsProperties. if (code[linkingObjectsPrefixLen] == '<') { NSString *classNameFromProtocol = [[NSString alloc] initWithBytes:code + linkingObjectsPrefixLen + 1 length:strlen(code + linkingObjectsPrefixLen) - 3 // drop trailing >" encoding:NSUTF8StringEncoding]; if (![_objectClassName isEqualToString:classNameFromProtocol]) { @throw RLMException(@"Property '%@' was declared with type RLMLinkingObjects<%@>, but a conflicting " "class name of '%@' was returned by +linkingObjectsProperties.", _name, classNameFromProtocol, _objectClassName); } } } else if (strcmp(code, "@\"NSNumber\"") == 0) { @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: NSNumber<RLMInt>.", _name); } else if (strcmp(code, "@\"RLMArray\"") == 0) { @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMArray<Person>.", _name); } else if (strcmp(code, "@\"RLMSet\"") == 0) { @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMSet<Person>.", _name); } else if (strcmp(code, "@\"RLMDictionary\"") == 0) { @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMDictionary<NSString *, Person *><RLMString, Person>.", _name); } else { NSString *className; Class cls = nil; if (code[1] == '\0') { className = @"id"; } else { // for objects strip the quotes and @ className = [rawType substringWithRange:NSMakeRange(2, rawType.length-3)]; cls = [RLMSchema classForString:className]; } if (!cls) { @throw RLMException(@"Property '%@' is declared as '%@', which is not a supported RLMObject property type. " @"All properties must be primitives, NSString, NSDate, NSData, NSNumber, RLMArray, RLMSet, RLMDictionary, RLMLinkingObjects, RLMDecimal128, RLMObjectId, or subclasses of RLMObject. " @"See https://realm.io/docs/objc/latest/api/Classes/RLMObject.html for more information.", _name, className); } _type = RLMPropertyTypeObject; _optional = true; _objectClassName = [cls className] ?: className; } return YES; } - (void)parseObjcProperty:(objc_property_t)property readOnly:(bool *)readOnly computed:(bool *)computed rawType:(NSString **)rawType { unsigned int count; objc_property_attribute_t *attrs = property_copyAttributeList(property, &count); *computed = true; for (size_t i = 0; i < count; ++i) { switch (*attrs[i].name) { case 'T': *rawType = @(attrs[i].value); break; case 'R': *readOnly = true; break; case 'G': _getterName = @(attrs[i].value); break; case 'S': _setterName = @(attrs[i].value); break; case 'V': // backing ivar name *computed = false; break; case '&': // retain/assign break; case 'C': // copy break; case 'D': // dynamic break; case 'N': // nonatomic break; case 'P': // GC'able break; case 'W': // weak break; default: break; } } free(attrs); } - (instancetype)initSwiftPropertyWithName:(NSString *)name indexed:(BOOL)indexed linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor property:(objc_property_t)property instance:(RLMObject *)obj { self = [super init]; if (!self) { return nil; } RLMValidateSwiftPropertyName(name); _name = name; _indexed = indexed; if (linkPropertyDescriptor) { _objectClassName = [linkPropertyDescriptor.objectClass className]; _linkOriginPropertyName = linkPropertyDescriptor.propertyName; } NSString *rawType; bool readOnly = false; bool isComputed = false; [self parseObjcProperty:property readOnly:&readOnly computed:&isComputed rawType:&rawType]; // Swift sometimes doesn't explicitly set the ivar name in the metadata, so check if // there's an ivar with the same name as the property. if (!readOnly && isComputed && class_getInstanceVariable([obj class], name.UTF8String)) { isComputed = false; } // Check if there's a storage ivar for a lazy property in this name. We don't honor // @lazy in managed objects, but allow it for unmanaged objects which are // subclasses of RLMObject (but not RealmSwift.Object). It's unclear if there's a // good reason for this difference. if (!readOnly && isComputed) { // Xcode 10 and earlier NSString *backingPropertyName = [NSString stringWithFormat:@"%@.storage", name]; isComputed = !class_getInstanceVariable([obj class], backingPropertyName.UTF8String); } if (!readOnly && isComputed) { // Xcode 11 NSString *backingPropertyName = [NSString stringWithFormat:@"$__lazy_storage_$_%@", name]; isComputed = !class_getInstanceVariable([obj class], backingPropertyName.UTF8String); } if (readOnly || isComputed) { return nil; } id propertyValue = [obj valueForKey:_name]; // FIXME: temporarily workaround added since Objective-C generics used in Swift show up as `@` // * broken starting in Swift 3.0 Xcode 8 b1 // * tested to still be broken in Swift 3.0 Xcode 8 b6 // * if the Realm Objective-C Swift tests pass with this removed, it's been fixed // * once it has been fixed, remove this entire conditional block (contents included) entirely // * Bug Report: SR-2031 https://bugs.swift.org/browse/SR-2031 if ([rawType isEqualToString:@"@"]) { if (propertyValue) { rawType = [NSString stringWithFormat:@"@\"%@\"", [propertyValue class]]; } else if (linkPropertyDescriptor) { // we're going to naively assume that the user used the correct type since we can't check it rawType = @"@\"RLMLinkingObjects\""; } } // convert array / set / dictionary types to objc variant if ([rawType isEqualToString:@"@\"RLMArray\""]) { RLMArray *value = propertyValue; _type = value.type; _optional = value.optional; _array = true; _objectClassName = value.objectClassName; if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) { @throw RLMException(@"Property '%@' is of type 'RLMArray<%@>' which is not a supported RLMArray object type. " @"RLMArrays can only contain instances of RLMObject subclasses. " @"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, _objectClassName); } } else if ([rawType isEqualToString:@"@\"RLMSet\""]) { RLMSet *value = propertyValue; _type = value.type; _optional = value.optional; _set = true; _objectClassName = value.objectClassName; if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) { @throw RLMException(@"Property '%@' is of type 'RLMSet<%@>' which is not a supported RLMSet object type. " @"RLMSets can only contain instances of RLMObject subclasses. " @"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, _objectClassName); } } else if ([rawType isEqualToString:@"@\"RLMDictionary\""]) { RLMDictionary *value = propertyValue; _type = value.type; _dictionaryKeyType = value.keyType; _optional = value.optional; _dictionary = true; _objectClassName = value.objectClassName; if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) { @throw RLMException(@"Property '%@' is of type 'RLMDictionary<KeyType, %@>' which is not a supported RLMDictionary object type. " @"RLMDictionarys can only contain instances of RLMObject subclasses. " @"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, _objectClassName); } } else if ([rawType isEqualToString:@"@\"NSNumber\""]) { const char *numberType = [propertyValue objCType]; if (!numberType) { @throw RLMException(@"Can't persist NSNumber without default value: use a Swift-native number type or provide a default value."); } _optional = true; switch (*numberType) { case 'i': case 'l': case 'q': _type = RLMPropertyTypeInt; break; case 'f': _type = RLMPropertyTypeFloat; break; case 'd': _type = RLMPropertyTypeDouble; break; case 'B': case 'c': _type = RLMPropertyTypeBool; break; default: @throw RLMException(@"Can't persist NSNumber of type '%s': only integers, floats, doubles, and bools are currently supported.", numberType); } } else if (![self setTypeFromRawType:rawType]) { @throw RLMException(@"Can't persist property '%@' with incompatible type. " "Add to Object.ignoredProperties() class method to ignore.", self.name); } if ([rawType isEqualToString:@"c"]) { // Check if it's a BOOL or Int8 by trying to set it to 2 and seeing if // it actually sets it to 1. [obj setValue:@2 forKey:name]; NSNumber *value = [obj valueForKey:name]; _type = value.intValue == 2 ? RLMPropertyTypeInt : RLMPropertyTypeBool; } // update getter/setter names [self updateAccessors]; return self; } - (instancetype)initWithName:(NSString *)name indexed:(BOOL)indexed linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor property:(objc_property_t)property { self = [super init]; if (!self) { return nil; } _name = name; _indexed = indexed; if (linkPropertyDescriptor) { _objectClassName = [linkPropertyDescriptor.objectClass className]; _linkOriginPropertyName = linkPropertyDescriptor.propertyName; } NSString *rawType; bool isReadOnly = false; bool isComputed = false; [self parseObjcProperty:property readOnly:&isReadOnly computed:&isComputed rawType:&rawType]; bool shouldBeTreatedAsComputedProperty = rawTypeShouldBeTreatedAsComputedProperty(rawType); if ((isReadOnly || isComputed) && !shouldBeTreatedAsComputedProperty) { return nil; } if (![self setTypeFromRawType:rawType]) { @throw RLMException(@"Can't persist property '%@' with incompatible type. " "Add to ignoredPropertyNames: method to ignore.", self.name); } if (!isReadOnly && shouldBeTreatedAsComputedProperty) { @throw RLMException(@"Property '%@' must be declared as readonly as %@ properties cannot be written to.", self.name, RLMTypeToString(_type)); } // update getter/setter names [self updateAccessors]; return self; } - (id)copyWithZone:(NSZone *)zone { RLMProperty *prop = [[RLMProperty allocWithZone:zone] init]; prop->_name = _name; prop->_columnName = _columnName; prop->_type = _type; prop->_objectClassName = _objectClassName; prop->_array = _array; prop->_set = _set; prop->_dictionary = _dictionary; prop->_dictionaryKeyType = _dictionaryKeyType; prop->_indexed = _indexed; prop->_getterName = _getterName; prop->_setterName = _setterName; prop->_getterSel = _getterSel; prop->_setterSel = _setterSel; prop->_isPrimary = _isPrimary; prop->_swiftAccessor = _swiftAccessor; prop->_swiftIvar = _swiftIvar; prop->_optional = _optional; prop->_linkOriginPropertyName = _linkOriginPropertyName; return prop; } - (RLMProperty *)copyWithNewName:(NSString *)name { RLMProperty *prop = [self copy]; prop.name = name; return prop; } - (BOOL)isEqual:(id)object { if (![object isKindOfClass:[RLMProperty class]]) { return NO; } return [self isEqualToProperty:object]; } - (BOOL)isEqualToProperty:(RLMProperty *)property { return _type == property->_type && _indexed == property->_indexed && _isPrimary == property->_isPrimary && _optional == property->_optional && [_name isEqualToString:property->_name] && (_objectClassName == property->_objectClassName || [_objectClassName isEqualToString:property->_objectClassName]) && (_linkOriginPropertyName == property->_linkOriginPropertyName || [_linkOriginPropertyName isEqualToString:property->_linkOriginPropertyName]); } - (BOOL)collection { return self.set || self.array || self.dictionary; } - (NSString *)description { NSString *objectClassName = @""; if (self.type == RLMPropertyTypeObject || self.type == RLMPropertyTypeLinkingObjects) { objectClassName = [NSString stringWithFormat: @"\tobjectClassName = %@;\n" @"\tlinkOriginPropertyName = %@;\n", self.objectClassName, self.linkOriginPropertyName]; } return [NSString stringWithFormat: @"%@ {\n" "\ttype = %@;\n" "%@" "\tindexed = %@;\n" "\tisPrimary = %@;\n" "\tarray = %@;\n" "\tset = %@;\n" "\tdictionary = %@;\n" "\toptional = %@;\n" "}", self.name, RLMTypeToString(self.type), objectClassName, self.indexed ? @"YES" : @"NO", self.isPrimary ? @"YES" : @"NO", self.array ? @"YES" : @"NO", self.set ? @"YES" : @"NO", self.dictionary ? @"YES" : @"NO", self.optional ? @"YES" : @"NO"]; } - (NSString *)columnName { return _columnName ?: _name; } - (realm::Property)objectStoreCopy:(RLMSchema *)schema { realm::Property p; p.name = self.columnName.UTF8String; if (_objectClassName) { RLMObjectSchema *targetSchema = schema[_objectClassName]; p.object_type = (targetSchema.objectName ?: _objectClassName).UTF8String; if (_linkOriginPropertyName) { p.link_origin_property_name = (targetSchema[_linkOriginPropertyName].columnName ?: _linkOriginPropertyName).UTF8String; } } p.is_indexed = static_cast<bool>(_indexed); p.type = static_cast<realm::PropertyType>(_type); if (_array) { p.type |= realm::PropertyType::Array; } if (_set) { p.type |= realm::PropertyType::Set; } if (_dictionary) { p.type |= realm::PropertyType::Dictionary; } if (_optional || p.type == realm::PropertyType::Mixed) { p.type |= realm::PropertyType::Nullable; } return p; } @end @implementation RLMPropertyDescriptor + (instancetype)descriptorWithClass:(Class)objectClass propertyName:(NSString *)propertyName { RLMPropertyDescriptor *descriptor = [[RLMPropertyDescriptor alloc] init]; descriptor->_objectClass = objectClass; descriptor->_propertyName = propertyName; return descriptor; } @end