added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMProperty.mm
diff --git a/iOS/Pods/Realm/Realm/RLMProperty.mm b/iOS/Pods/Realm/Realm/RLMProperty.mm
new file mode 100644 (file)
index 0000000..e9d4c08
--- /dev/null
@@ -0,0 +1,641 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// 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 "RLMObject_Private.h"
+#import "RLMOptionalBase.h"
+#import "RLMSchema_Private.h"
+#import "RLMSwiftSupport.h"
+#import "RLMUtil.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;
+    }
+    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 'N':
+                // nonatomic
+                break;
+            case 'D':
+                // dynamic
+                break;
+            case 'G':
+                _getterName = @(attrs[i].value);
+                break;
+            case 'S':
+                _setterName = @(attrs[i].value);
+                break;
+            case 'V': // backing ivar name
+                *computed = false;
+                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];
+    if (!readOnly && isComputed) {
+        // Check for lazy property.
+        NSString *backingPropertyName = [NSString stringWithFormat:@"%@.storage", name];
+        if (class_getInstanceVariable([obj class], backingPropertyName.UTF8String)) {
+            isComputed = false;
+        }
+    }
+
+    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->_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"];
+}
+
+- (realm::Property)objectStoreCopy {
+    realm::Property p;
+    p.name = _name.UTF8String;
+    p.object_type = _objectClassName ? _objectClassName.UTF8String : "";
+    p.is_indexed = (bool)_indexed;
+    p.link_origin_property_name = _linkOriginPropertyName ? _linkOriginPropertyName.UTF8String : "";
+    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