added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMProperty.mm
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2014 Realm Inc.
4 //
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
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
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.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #import "RLMProperty_Private.hpp"
20
21 #import "RLMArray_Private.hpp"
22 #import "RLMListBase.h"
23 #import "RLMObject.h"
24 #import "RLMObject_Private.h"
25 #import "RLMOptionalBase.h"
26 #import "RLMSchema_Private.h"
27 #import "RLMSwiftSupport.h"
28 #import "RLMUtil.hpp"
29
30 static_assert((int)RLMPropertyTypeInt    == (int)realm::PropertyType::Int, "");
31 static_assert((int)RLMPropertyTypeBool   == (int)realm::PropertyType::Bool, "");
32 static_assert((int)RLMPropertyTypeFloat  == (int)realm::PropertyType::Float, "");
33 static_assert((int)RLMPropertyTypeDouble == (int)realm::PropertyType::Double, "");
34 static_assert((int)RLMPropertyTypeString == (int)realm::PropertyType::String, "");
35 static_assert((int)RLMPropertyTypeData   == (int)realm::PropertyType::Data, "");
36 static_assert((int)RLMPropertyTypeDate   == (int)realm::PropertyType::Date, "");
37 static_assert((int)RLMPropertyTypeObject == (int)realm::PropertyType::Object, "");
38
39 BOOL RLMPropertyTypeIsComputed(RLMPropertyType propertyType) {
40     return propertyType == RLMPropertyTypeLinkingObjects;
41 }
42
43 // Swift obeys the ARC naming conventions for method families (except for init)
44 // but the end result doesn't really work (using KVC on a method returning a
45 // retained value results in a leak, but not returning a retained value results
46 // in crashes). Objective-C makes properties with naming fitting the method
47 // families a compile error, so we just disallow them in Swift as well.
48 // http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families
49 void RLMValidateSwiftPropertyName(NSString *name) {
50     // To belong to a method family, the property name must begin with the family
51     // name followed by a non-lowercase letter (or nothing), with an optional
52     // leading underscore
53     const char *str = name.UTF8String;
54     if (str[0] == '_')
55         ++str;
56     auto nameSize = strlen(str);
57
58     // Note that "init" is deliberately not in this list because Swift does not
59     // infer family membership for it.
60     for (auto family : {"alloc", "new", "copy", "mutableCopy"}) {
61         auto familySize = strlen(family);
62         if (nameSize < familySize || !std::equal(str, str + familySize, family)) {
63             continue;
64         }
65         if (familySize == nameSize || !islower(str[familySize])) {
66             @throw RLMException(@"Property names beginning with '%s' are not "
67                                  "supported. Swift follows ARC's ownership "
68                                  "rules for methods based on their name, which "
69                                  "results in memory leaks when accessing "
70                                  "properties which return retained values via KVC.",
71                                 family);
72         }
73         return;
74     }
75 }
76
77 static bool rawTypeShouldBeTreatedAsComputedProperty(NSString *rawType) {
78     return [rawType isEqualToString:@"@\"RLMLinkingObjects\""] || [rawType hasPrefix:@"@\"RLMLinkingObjects<"];
79 }
80
81 @implementation RLMProperty
82
83 + (instancetype)propertyForObjectStoreProperty:(const realm::Property &)prop {
84     auto ret = [[RLMProperty alloc] initWithName:@(prop.name.c_str())
85                                             type:static_cast<RLMPropertyType>(prop.type & ~realm::PropertyType::Flags)
86                                  objectClassName:prop.object_type.length() ? @(prop.object_type.c_str()) : nil
87                           linkOriginPropertyName:prop.link_origin_property_name.length() ? @(prop.link_origin_property_name.c_str()) : nil
88                                          indexed:prop.is_indexed
89                                         optional:is_nullable(prop.type)];
90     if (is_array(prop.type)) {
91         ret->_array = true;
92     }
93     return ret;
94 }
95
96 - (instancetype)initWithName:(NSString *)name
97                         type:(RLMPropertyType)type
98              objectClassName:(NSString *)objectClassName
99       linkOriginPropertyName:(NSString *)linkOriginPropertyName
100                      indexed:(BOOL)indexed
101                     optional:(BOOL)optional {
102     self = [super init];
103     if (self) {
104         _name = name;
105         _type = type;
106         _objectClassName = objectClassName;
107         _linkOriginPropertyName = linkOriginPropertyName;
108         _indexed = indexed;
109         _optional = optional;
110         [self updateAccessors];
111     }
112
113     return self;
114 }
115
116 - (void)setName:(NSString *)name {
117     _name = name;
118     [self updateAccessors];
119 }
120
121 - (void)updateAccessors {
122     // populate getter/setter names if generic
123     if (!_getterName) {
124         _getterName = _name;
125     }
126     if (!_setterName) {
127         // Objective-C setters only capitalize the first letter of the property name if it falls between 'a' and 'z'
128         int asciiCode = [_name characterAtIndex:0];
129         BOOL shouldUppercase = asciiCode >= 'a' && asciiCode <= 'z';
130         NSString *firstChar = [_name substringToIndex:1];
131         firstChar = shouldUppercase ? firstChar.uppercaseString : firstChar;
132         _setterName = [NSString stringWithFormat:@"set%@%@:", firstChar, [_name substringFromIndex:1]];
133     }
134
135     _getterSel = NSSelectorFromString(_getterName);
136     _setterSel = NSSelectorFromString(_setterName);
137 }
138
139 static realm::util::Optional<RLMPropertyType> typeFromProtocolString(const char *type) {
140     if (strncmp(type, "RLM", 3)) {
141         return realm::none;
142     }
143     type += 3;
144     if (strcmp(type, "Int>\"") == 0) {
145         return RLMPropertyTypeInt;
146     }
147     if (strcmp(type, "Float>\"") == 0) {
148         return RLMPropertyTypeFloat;
149     }
150     if (strcmp(type, "Double>\"") == 0) {
151         return RLMPropertyTypeDouble;
152     }
153     if (strcmp(type, "Bool>\"") == 0) {
154         return RLMPropertyTypeBool;
155     }
156     if (strcmp(type, "String>\"") == 0) {
157         return RLMPropertyTypeString;
158     }
159     if (strcmp(type, "Data>\"") == 0) {
160         return RLMPropertyTypeData;
161     }
162     if (strcmp(type, "Date>\"") == 0) {
163         return RLMPropertyTypeDate;
164     }
165     return realm::none;
166 }
167
168 // determine RLMPropertyType from objc code - returns true if valid type was found/set
169 - (BOOL)setTypeFromRawType:(NSString *)rawType {
170     const char *code = rawType.UTF8String;
171     switch (*code) {
172         case 's':   // short
173         case 'i':   // int
174         case 'l':   // long
175         case 'q':   // long long
176             _type = RLMPropertyTypeInt;
177             return YES;
178         case 'f':
179             _type = RLMPropertyTypeFloat;
180             return YES;
181         case 'd':
182             _type = RLMPropertyTypeDouble;
183             return YES;
184         case 'c':   // BOOL is stored as char - since rlm has no char type this is ok
185         case 'B':
186             _type = RLMPropertyTypeBool;
187             return YES;
188         case '@':
189             break;
190         default:
191             return NO;
192     }
193
194     _optional = true;
195     static const char arrayPrefix[] = "@\"RLMArray<";
196     static const int arrayPrefixLen = sizeof(arrayPrefix) - 1;
197
198     static const char numberPrefix[] = "@\"NSNumber<";
199     static const int numberPrefixLen = sizeof(numberPrefix) - 1;
200
201     static const char linkingObjectsPrefix[] = "@\"RLMLinkingObjects";
202     static const int linkingObjectsPrefixLen = sizeof(linkingObjectsPrefix) - 1;
203
204     if (strcmp(code, "@\"NSString\"") == 0) {
205         _type = RLMPropertyTypeString;
206     }
207     else if (strcmp(code, "@\"NSDate\"") == 0) {
208         _type = RLMPropertyTypeDate;
209     }
210     else if (strcmp(code, "@\"NSData\"") == 0) {
211         _type = RLMPropertyTypeData;
212     }
213     else if (strncmp(code, arrayPrefix, arrayPrefixLen) == 0) {
214         _array = true;
215         if (auto type = typeFromProtocolString(code + arrayPrefixLen)) {
216             _type = *type;
217             return YES;
218         }
219
220         // get object class from type string - @"RLMArray<objectClassName>"
221         _objectClassName = [[NSString alloc] initWithBytes:code + arrayPrefixLen
222                                                     length:strlen(code + arrayPrefixLen) - 2 // drop trailing >"
223                                                   encoding:NSUTF8StringEncoding];
224
225         if ([RLMSchema classForString:_objectClassName]) {
226             _optional = false;
227             _type = RLMPropertyTypeObject;
228             return YES;
229         }
230         @throw RLMException(@"Property '%@' is of type 'RLMArray<%@>' which is not a supported RLMArray object type. "
231                             @"RLMArrays can only contain instances of RLMObject subclasses. "
232                             @"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, _objectClassName);
233     }
234     else if (strncmp(code, numberPrefix, numberPrefixLen) == 0) {
235         auto type = typeFromProtocolString(code + numberPrefixLen);
236         if (type && (*type == RLMPropertyTypeInt || *type == RLMPropertyTypeFloat || *type == RLMPropertyTypeDouble || *type == RLMPropertyTypeBool)) {
237             _type = *type;
238             return YES;
239         }
240         @throw RLMException(@"Property '%@' is of type %s which is not a supported NSNumber object type. "
241                             @"NSNumbers can only be RLMInt, RLMFloat, RLMDouble, and RLMBool at the moment. "
242                             @"See https://realm.io/docs/objc/latest for more information.", _name, code + 1);
243     }
244     else if (strncmp(code, linkingObjectsPrefix, linkingObjectsPrefixLen) == 0 &&
245              (code[linkingObjectsPrefixLen] == '"' || code[linkingObjectsPrefixLen] == '<')) {
246         _type = RLMPropertyTypeLinkingObjects;
247         _optional = false;
248         _array = true;
249
250         if (!_objectClassName || !_linkOriginPropertyName) {
251             @throw RLMException(@"Property '%@' is of type RLMLinkingObjects but +linkingObjectsProperties did not specify the class "
252                                 "or property that is the origin of the link.", _name);
253         }
254
255         // If the property was declared with a protocol indicating the contained type, validate that it matches
256         // the class from the dictionary returned by +linkingObjectsProperties.
257         if (code[linkingObjectsPrefixLen] == '<') {
258             NSString *classNameFromProtocol = [[NSString alloc] initWithBytes:code + linkingObjectsPrefixLen + 1
259                                                                        length:strlen(code + linkingObjectsPrefixLen) - 3 // drop trailing >"
260                                                                      encoding:NSUTF8StringEncoding];
261             if (![_objectClassName isEqualToString:classNameFromProtocol]) {
262                 @throw RLMException(@"Property '%@' was declared with type RLMLinkingObjects<%@>, but a conflicting "
263                                     "class name of '%@' was returned by +linkingObjectsProperties.", _name,
264                                     classNameFromProtocol, _objectClassName);
265             }
266         }
267     }
268     else if (strcmp(code, "@\"NSNumber\"") == 0) {
269         @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: NSNumber<RLMInt>.", _name);
270     }
271     else if (strcmp(code, "@\"RLMArray\"") == 0) {
272         @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMArray<Person>.", _name);
273     }
274     else {
275         NSString *className;
276         Class cls = nil;
277         if (code[1] == '\0') {
278             className = @"id";
279         }
280         else {
281             // for objects strip the quotes and @
282             className = [rawType substringWithRange:NSMakeRange(2, rawType.length-3)];
283             cls = [RLMSchema classForString:className];
284         }
285
286         if (!cls) {
287             @throw RLMException(@"Property '%@' is declared as '%@', which is not a supported RLMObject property type. "
288                                 @"All properties must be primitives, NSString, NSDate, NSData, NSNumber, RLMArray, RLMLinkingObjects, or subclasses of RLMObject. "
289                                 @"See https://realm.io/docs/objc/latest/api/Classes/RLMObject.html for more information.", _name, className);
290         }
291
292         _type = RLMPropertyTypeObject;
293         _optional = true;
294         _objectClassName = [cls className] ?: className;
295     }
296     return YES;
297 }
298
299 - (void)parseObjcProperty:(objc_property_t)property
300                  readOnly:(bool *)readOnly
301                  computed:(bool *)computed
302                   rawType:(NSString **)rawType {
303     unsigned int count;
304     objc_property_attribute_t *attrs = property_copyAttributeList(property, &count);
305
306     *computed = true;
307     for (size_t i = 0; i < count; ++i) {
308         switch (*attrs[i].name) {
309             case 'T':
310                 *rawType = @(attrs[i].value);
311                 break;
312             case 'R':
313                 *readOnly = true;
314                 break;
315             case 'N':
316                 // nonatomic
317                 break;
318             case 'D':
319                 // dynamic
320                 break;
321             case 'G':
322                 _getterName = @(attrs[i].value);
323                 break;
324             case 'S':
325                 _setterName = @(attrs[i].value);
326                 break;
327             case 'V': // backing ivar name
328                 *computed = false;
329                 break;
330             default:
331                 break;
332         }
333     }
334     free(attrs);
335 }
336
337 - (instancetype)initSwiftPropertyWithName:(NSString *)name
338                                   indexed:(BOOL)indexed
339                    linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor
340                                  property:(objc_property_t)property
341                                  instance:(RLMObject *)obj {
342     self = [super init];
343     if (!self) {
344         return nil;
345     }
346
347     RLMValidateSwiftPropertyName(name);
348
349     _name = name;
350     _indexed = indexed;
351
352     if (linkPropertyDescriptor) {
353         _objectClassName = [linkPropertyDescriptor.objectClass className];
354         _linkOriginPropertyName = linkPropertyDescriptor.propertyName;
355     }
356
357     NSString *rawType;
358     bool readOnly = false;
359     bool isComputed = false;
360     [self parseObjcProperty:property readOnly:&readOnly computed:&isComputed rawType:&rawType];
361     if (!readOnly && isComputed) {
362         // Check for lazy property.
363         NSString *backingPropertyName = [NSString stringWithFormat:@"%@.storage", name];
364         if (class_getInstanceVariable([obj class], backingPropertyName.UTF8String)) {
365             isComputed = false;
366         }
367     }
368
369     if (readOnly || isComputed) {
370         return nil;
371     }
372
373     id propertyValue = [obj valueForKey:_name];
374
375     // FIXME: temporarily workaround added since Objective-C generics used in Swift show up as `@`
376     //        * broken starting in Swift 3.0 Xcode 8 b1
377     //        * tested to still be broken in Swift 3.0 Xcode 8 b6
378     //        * if the Realm Objective-C Swift tests pass with this removed, it's been fixed
379     //        * once it has been fixed, remove this entire conditional block (contents included) entirely
380     //        * Bug Report: SR-2031 https://bugs.swift.org/browse/SR-2031
381     if ([rawType isEqualToString:@"@"]) {
382         if (propertyValue) {
383             rawType = [NSString stringWithFormat:@"@\"%@\"", [propertyValue class]];
384         } else if (linkPropertyDescriptor) {
385             // we're going to naively assume that the user used the correct type since we can't check it
386             rawType = @"@\"RLMLinkingObjects\"";
387         }
388     }
389
390     // convert array types to objc variant
391     if ([rawType isEqualToString:@"@\"RLMArray\""]) {
392         RLMArray *value = propertyValue;
393         _type = value.type;
394         _optional = value.optional;
395         _array = true;
396         _objectClassName = value.objectClassName;
397         if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) {
398             @throw RLMException(@"Property '%@' is of type 'RLMArray<%@>' which is not a supported RLMArray object type. "
399                                 @"RLMArrays can only contain instances of RLMObject subclasses. "
400                                 @"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, _objectClassName);
401         }
402     }
403     else if ([rawType isEqualToString:@"@\"NSNumber\""]) {
404         const char *numberType = [propertyValue objCType];
405         if (!numberType) {
406             @throw RLMException(@"Can't persist NSNumber without default value: use a Swift-native number type or provide a default value.");
407         }
408         _optional = true;
409         switch (*numberType) {
410             case 'i': case 'l': case 'q':
411                 _type = RLMPropertyTypeInt;
412                 break;
413             case 'f':
414                 _type = RLMPropertyTypeFloat;
415                 break;
416             case 'd':
417                 _type = RLMPropertyTypeDouble;
418                 break;
419             case 'B': case 'c':
420                 _type = RLMPropertyTypeBool;
421                 break;
422             default:
423                 @throw RLMException(@"Can't persist NSNumber of type '%s': only integers, floats, doubles, and bools are currently supported.", numberType);
424         }
425     }
426     else if (![self setTypeFromRawType:rawType]) {
427         @throw RLMException(@"Can't persist property '%@' with incompatible type. "
428                             "Add to Object.ignoredProperties() class method to ignore.",
429                             self.name);
430     }
431
432     if ([rawType isEqualToString:@"c"]) {
433         // Check if it's a BOOL or Int8 by trying to set it to 2 and seeing if
434         // it actually sets it to 1.
435         [obj setValue:@2 forKey:name];
436         NSNumber *value = [obj valueForKey:name];
437         _type = value.intValue == 2 ? RLMPropertyTypeInt : RLMPropertyTypeBool;
438     }
439
440     // update getter/setter names
441     [self updateAccessors];
442
443     return self;
444 }
445
446 - (instancetype)initWithName:(NSString *)name
447                      indexed:(BOOL)indexed
448       linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor
449                     property:(objc_property_t)property
450 {
451     self = [super init];
452     if (!self) {
453         return nil;
454     }
455
456     _name = name;
457     _indexed = indexed;
458
459     if (linkPropertyDescriptor) {
460         _objectClassName = [linkPropertyDescriptor.objectClass className];
461         _linkOriginPropertyName = linkPropertyDescriptor.propertyName;
462     }
463
464     NSString *rawType;
465     bool isReadOnly = false;
466     bool isComputed = false;
467     [self parseObjcProperty:property readOnly:&isReadOnly computed:&isComputed rawType:&rawType];
468     bool shouldBeTreatedAsComputedProperty = rawTypeShouldBeTreatedAsComputedProperty(rawType);
469     if ((isReadOnly || isComputed) && !shouldBeTreatedAsComputedProperty) {
470         return nil;
471     }
472
473     if (![self setTypeFromRawType:rawType]) {
474         @throw RLMException(@"Can't persist property '%@' with incompatible type. "
475                              "Add to ignoredPropertyNames: method to ignore.", self.name);
476     }
477
478     if (!isReadOnly && shouldBeTreatedAsComputedProperty) {
479         @throw RLMException(@"Property '%@' must be declared as readonly as %@ properties cannot be written to.",
480                             self.name, RLMTypeToString(_type));
481     }
482
483     // update getter/setter names
484     [self updateAccessors];
485
486     return self;
487 }
488
489 - (instancetype)initSwiftListPropertyWithName:(NSString *)name
490                                      instance:(id)object {
491     self = [super init];
492     if (!self) {
493         return nil;
494     }
495     _name = name;
496     _array = true;
497     _swiftIvar = class_getInstanceVariable([object class], name.UTF8String);
498
499     RLMArray *array = [object_getIvar(object, _swiftIvar) _rlmArray];
500     _type = array.type;
501     _optional = array.optional;
502     _objectClassName = array.objectClassName;
503
504     // no obj-c property for generic lists, and thus no getter/setter names
505
506     return self;
507 }
508
509 - (instancetype)initSwiftOptionalPropertyWithName:(NSString *)name
510                                           indexed:(BOOL)indexed
511                                              ivar:(Ivar)ivar
512                                      propertyType:(RLMPropertyType)propertyType {
513     self = [super init];
514     if (!self) {
515         return nil;
516     }
517
518     _name = name;
519     _type = propertyType;
520     _indexed = indexed;
521     _swiftIvar = ivar;
522     _optional = true;
523
524     // no obj-c property for generic optionals, and thus no getter/setter names
525
526     return self;
527 }
528
529 - (instancetype)initSwiftLinkingObjectsPropertyWithName:(NSString *)name
530                                                    ivar:(Ivar)ivar
531                                         objectClassName:(NSString *)objectClassName
532                                  linkOriginPropertyName:(NSString *)linkOriginPropertyName {
533     self = [super init];
534     if (!self) {
535         return nil;
536     }
537
538     _name = name;
539     _type = RLMPropertyTypeLinkingObjects;
540     _array = true;
541     _objectClassName = objectClassName;
542     _linkOriginPropertyName = linkOriginPropertyName;
543     _swiftIvar = ivar;
544
545     // no obj-c property for generic linking objects properties, and thus no getter/setter names
546
547     return self;
548 }
549
550 - (id)copyWithZone:(NSZone *)zone {
551     RLMProperty *prop = [[RLMProperty allocWithZone:zone] init];
552     prop->_name = _name;
553     prop->_type = _type;
554     prop->_objectClassName = _objectClassName;
555     prop->_array = _array;
556     prop->_indexed = _indexed;
557     prop->_getterName = _getterName;
558     prop->_setterName = _setterName;
559     prop->_getterSel = _getterSel;
560     prop->_setterSel = _setterSel;
561     prop->_isPrimary = _isPrimary;
562     prop->_swiftIvar = _swiftIvar;
563     prop->_optional = _optional;
564     prop->_linkOriginPropertyName = _linkOriginPropertyName;
565
566     return prop;
567 }
568
569 - (RLMProperty *)copyWithNewName:(NSString *)name {
570     RLMProperty *prop = [self copy];
571     prop.name = name;
572     return prop;
573 }
574
575 - (BOOL)isEqual:(id)object {
576     if (![object isKindOfClass:[RLMProperty class]]) {
577         return NO;
578     }
579
580     return [self isEqualToProperty:object];
581 }
582
583 - (BOOL)isEqualToProperty:(RLMProperty *)property {
584     return _type == property->_type
585         && _indexed == property->_indexed
586         && _isPrimary == property->_isPrimary
587         && _optional == property->_optional
588         && [_name isEqualToString:property->_name]
589         && (_objectClassName == property->_objectClassName  || [_objectClassName isEqualToString:property->_objectClassName])
590         && (_linkOriginPropertyName == property->_linkOriginPropertyName ||
591             [_linkOriginPropertyName isEqualToString:property->_linkOriginPropertyName]);
592 }
593
594 - (NSString *)description {
595     return [NSString stringWithFormat:
596             @"%@ {\n"
597              "\ttype = %@;\n"
598              "\tobjectClassName = %@;\n"
599              "\tlinkOriginPropertyName = %@;\n"
600              "\tindexed = %@;\n"
601              "\tisPrimary = %@;\n"
602              "\tarray = %@;\n"
603              "\toptional = %@;\n"
604              "}",
605             self.name, RLMTypeToString(self.type), self.objectClassName,
606             self.linkOriginPropertyName,
607             self.indexed ? @"YES" : @"NO",
608             self.isPrimary ? @"YES" : @"NO",
609             self.array ? @"YES" : @"NO",
610             self.optional ? @"YES" : @"NO"];
611 }
612
613 - (realm::Property)objectStoreCopy {
614     realm::Property p;
615     p.name = _name.UTF8String;
616     p.object_type = _objectClassName ? _objectClassName.UTF8String : "";
617     p.is_indexed = (bool)_indexed;
618     p.link_origin_property_name = _linkOriginPropertyName ? _linkOriginPropertyName.UTF8String : "";
619     p.type = static_cast<realm::PropertyType>(_type);
620     if (_array) {
621         p.type |= realm::PropertyType::Array;
622     }
623     if (_optional) {
624         p.type |= realm::PropertyType::Nullable;
625     }
626     return p;
627 }
628
629 @end
630
631 @implementation RLMPropertyDescriptor
632
633 + (instancetype)descriptorWithClass:(Class)objectClass propertyName:(NSString *)propertyName
634 {
635     RLMPropertyDescriptor *descriptor = [[RLMPropertyDescriptor alloc] init];
636     descriptor->_objectClass = objectClass;
637     descriptor->_propertyName = propertyName;
638     return descriptor;
639 }
640
641 @end