filesbox/app/filesbox_ios/FilesBox/Pods/Realm/Realm/RLMProperty.mm
2023-09-21 10:53:23 +08:00

685 lines
26 KiB
Plaintext

////////////////////////////////////////////////////////////////////////////
//
// 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 "RLMListBase.h"
#import "RLMObject.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObject_Private.h"
#import "RLMSchema_Private.h"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
#import "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, "");
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:is_nullable(prop.type)];
if (is_array(prop.type)) {
ret->_array = 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)setName:(NSString *)name {
_name = name;
[self updateAccessors];
}
- (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 (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;
}
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 numberPrefix[] = "@\"NSNumber<";
static const int numberPrefixLen = sizeof(numberPrefix) - 1;
static const char linkingObjectsPrefix[] = "@\"RLMLinkingObjects";
static const int linkingObjectsPrefixLen = sizeof(linkingObjectsPrefix) - 1;
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 (strncmp(code, arrayPrefix, arrayPrefixLen) == 0) {
_array = true;
if (auto type = typeFromProtocolString(code + arrayPrefixLen)) {
_type = *type;
return YES;
}
// get object class from type string - @"RLMArray<objectClassName>"
_objectClassName = [[NSString alloc] initWithBytes:code + arrayPrefixLen
length:strlen(code + arrayPrefixLen) - 2 // drop trailing >"
encoding:NSUTF8StringEncoding];
if ([RLMSchema classForString:_objectClassName]) {
_optional = false;
_type = RLMPropertyTypeObject;
return YES;
}
@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 (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 {
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, RLMLinkingObjects, 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 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:@"@\"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;
}
- (instancetype)initSwiftListPropertyWithName:(NSString *)name
instance:(id)object {
self = [super init];
if (!self) {
return nil;
}
_name = name;
_array = true;
_swiftIvar = class_getInstanceVariable([object class], name.UTF8String);
RLMArray *array = [object_getIvar(object, _swiftIvar) _rlmArray];
_type = array.type;
_optional = array.optional;
_objectClassName = array.objectClassName;
// no obj-c property for generic lists, and thus no getter/setter names
return self;
}
- (instancetype)initSwiftOptionalPropertyWithName:(NSString *)name
indexed:(BOOL)indexed
ivar:(Ivar)ivar
propertyType:(RLMPropertyType)propertyType {
self = [super init];
if (!self) {
return nil;
}
_name = name;
_type = propertyType;
_indexed = indexed;
_swiftIvar = ivar;
_optional = true;
// no obj-c property for generic optionals, and thus no getter/setter names
return self;
}
- (instancetype)initSwiftLinkingObjectsPropertyWithName:(NSString *)name
ivar:(Ivar)ivar
objectClassName:(NSString *)objectClassName
linkOriginPropertyName:(NSString *)linkOriginPropertyName {
self = [super init];
if (!self) {
return nil;
}
_name = name;
_type = RLMPropertyTypeLinkingObjects;
_array = true;
_objectClassName = objectClassName;
_linkOriginPropertyName = linkOriginPropertyName;
_swiftIvar = ivar;
// no obj-c property for generic linking objects properties, and thus no getter/setter names
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->_indexed = _indexed;
prop->_getterName = _getterName;
prop->_setterName = _setterName;
prop->_getterSel = _getterSel;
prop->_setterSel = _setterSel;
prop->_isPrimary = _isPrimary;
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]);
}
- (NSString *)description {
return [NSString stringWithFormat:
@"%@ {\n"
"\ttype = %@;\n"
"\tobjectClassName = %@;\n"
"\tlinkOriginPropertyName = %@;\n"
"\tindexed = %@;\n"
"\tisPrimary = %@;\n"
"\tarray = %@;\n"
"\toptional = %@;\n"
"}",
self.name, RLMTypeToString(self.type), self.objectClassName,
self.linkOriginPropertyName,
self.indexed ? @"YES" : @"NO",
self.isPrimary ? @"YES" : @"NO",
self.array ? @"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 (_optional) {
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