RLMProperty.mm 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  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. #import "RLMProperty_Private.hpp"
  19. #import "RLMArray_Private.hpp"
  20. #import "RLMDictionary_Private.hpp"
  21. #import "RLMObject.h"
  22. #import "RLMObjectSchema_Private.hpp"
  23. #import "RLMObject_Private.h"
  24. #import "RLMSchema_Private.h"
  25. #import "RLMSet_Private.hpp"
  26. #import "RLMSwiftSupport.h"
  27. #import "RLMUtil.hpp"
  28. #import <realm/object-store/property.hpp>
  29. static_assert((int)RLMPropertyTypeInt == (int)realm::PropertyType::Int);
  30. static_assert((int)RLMPropertyTypeBool == (int)realm::PropertyType::Bool);
  31. static_assert((int)RLMPropertyTypeFloat == (int)realm::PropertyType::Float);
  32. static_assert((int)RLMPropertyTypeDouble == (int)realm::PropertyType::Double);
  33. static_assert((int)RLMPropertyTypeString == (int)realm::PropertyType::String);
  34. static_assert((int)RLMPropertyTypeData == (int)realm::PropertyType::Data);
  35. static_assert((int)RLMPropertyTypeDate == (int)realm::PropertyType::Date);
  36. static_assert((int)RLMPropertyTypeObject == (int)realm::PropertyType::Object);
  37. static_assert((int)RLMPropertyTypeObjectId == (int)realm::PropertyType::ObjectId);
  38. static_assert((int)RLMPropertyTypeDecimal128 == (int)realm::PropertyType::Decimal);
  39. static_assert((int)RLMPropertyTypeUUID == (int)realm::PropertyType::UUID);
  40. static_assert((int)RLMPropertyTypeAny == (int)realm::PropertyType::Mixed);
  41. BOOL RLMPropertyTypeIsComputed(RLMPropertyType propertyType) {
  42. return propertyType == RLMPropertyTypeLinkingObjects;
  43. }
  44. // Swift obeys the ARC naming conventions for method families (except for init)
  45. // but the end result doesn't really work (using KVC on a method returning a
  46. // retained value results in a leak, but not returning a retained value results
  47. // in crashes). Objective-C makes properties with naming fitting the method
  48. // families a compile error, so we just disallow them in Swift as well.
  49. // http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families
  50. void RLMValidateSwiftPropertyName(NSString *name) {
  51. // To belong to a method family, the property name must begin with the family
  52. // name followed by a non-lowercase letter (or nothing), with an optional
  53. // leading underscore
  54. const char *str = name.UTF8String;
  55. if (str[0] == '_')
  56. ++str;
  57. auto nameSize = strlen(str);
  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. static bool rawTypeShouldBeTreatedAsComputedProperty(NSString *rawType) {
  77. return [rawType isEqualToString:@"@\"RLMLinkingObjects\""] || [rawType hasPrefix:@"@\"RLMLinkingObjects<"];
  78. }
  79. @implementation RLMProperty
  80. + (instancetype)propertyForObjectStoreProperty:(const realm::Property &)prop {
  81. auto ret = [[RLMProperty alloc] initWithName:@(prop.name.c_str())
  82. type:static_cast<RLMPropertyType>(prop.type & ~realm::PropertyType::Flags)
  83. objectClassName:prop.object_type.length() ? @(prop.object_type.c_str()) : nil
  84. linkOriginPropertyName:prop.link_origin_property_name.length() ? @(prop.link_origin_property_name.c_str()) : nil
  85. indexed:prop.is_indexed
  86. optional:isNullable(prop.type)];
  87. if (is_array(prop.type)) {
  88. ret->_array = true;
  89. }
  90. if (is_set(prop.type)) {
  91. ret->_set = true;
  92. }
  93. if (is_dictionary(prop.type)) {
  94. // TODO: We need a way to store the dictionary
  95. // key type in realm::Property once we support more
  96. // key types.
  97. ret->_dictionaryKeyType = RLMPropertyTypeString;
  98. ret->_dictionary = true;
  99. }
  100. if (!prop.public_name.empty()) {
  101. ret->_columnName = ret->_name;
  102. ret->_name = @(prop.public_name.c_str());
  103. }
  104. return ret;
  105. }
  106. - (instancetype)initWithName:(NSString *)name
  107. type:(RLMPropertyType)type
  108. objectClassName:(NSString *)objectClassName
  109. linkOriginPropertyName:(NSString *)linkOriginPropertyName
  110. indexed:(BOOL)indexed
  111. optional:(BOOL)optional {
  112. self = [super init];
  113. if (self) {
  114. _name = name;
  115. _type = type;
  116. _objectClassName = objectClassName;
  117. _linkOriginPropertyName = linkOriginPropertyName;
  118. _indexed = indexed;
  119. _optional = optional;
  120. [self updateAccessors];
  121. }
  122. return self;
  123. }
  124. - (void)updateAccessors {
  125. // populate getter/setter names if generic
  126. if (!_getterName) {
  127. _getterName = _name;
  128. }
  129. if (!_setterName) {
  130. // Objective-C setters only capitalize the first letter of the property name if it falls between 'a' and 'z'
  131. int asciiCode = [_name characterAtIndex:0];
  132. BOOL shouldUppercase = asciiCode >= 'a' && asciiCode <= 'z';
  133. NSString *firstChar = [_name substringToIndex:1];
  134. firstChar = shouldUppercase ? firstChar.uppercaseString : firstChar;
  135. _setterName = [NSString stringWithFormat:@"set%@%@:", firstChar, [_name substringFromIndex:1]];
  136. }
  137. _getterSel = NSSelectorFromString(_getterName);
  138. _setterSel = NSSelectorFromString(_setterName);
  139. }
  140. static std::optional<RLMPropertyType> typeFromProtocolString(const char *type) {
  141. if (strcmp(type, "RLMValue>\"") == 0) {
  142. return RLMPropertyTypeAny;
  143. }
  144. if (strncmp(type, "RLM", 3)) {
  145. return realm::none;
  146. }
  147. type += 3;
  148. if (strcmp(type, "Int>\"") == 0) {
  149. return RLMPropertyTypeInt;
  150. }
  151. if (strcmp(type, "Float>\"") == 0) {
  152. return RLMPropertyTypeFloat;
  153. }
  154. if (strcmp(type, "Double>\"") == 0) {
  155. return RLMPropertyTypeDouble;
  156. }
  157. if (strcmp(type, "Bool>\"") == 0) {
  158. return RLMPropertyTypeBool;
  159. }
  160. if (strcmp(type, "String>\"") == 0) {
  161. return RLMPropertyTypeString;
  162. }
  163. if (strcmp(type, "Data>\"") == 0) {
  164. return RLMPropertyTypeData;
  165. }
  166. if (strcmp(type, "Date>\"") == 0) {
  167. return RLMPropertyTypeDate;
  168. }
  169. if (strcmp(type, "Decimal128>\"") == 0) {
  170. return RLMPropertyTypeDecimal128;
  171. }
  172. if (strcmp(type, "ObjectId>\"") == 0) {
  173. return RLMPropertyTypeObjectId;
  174. }
  175. if (strcmp(type, "UUID>\"") == 0) {
  176. return RLMPropertyTypeUUID;
  177. }
  178. return realm::none;
  179. }
  180. // determine RLMPropertyType from objc code - returns true if valid type was found/set
  181. - (BOOL)setTypeFromRawType:(NSString *)rawType {
  182. const char *code = rawType.UTF8String;
  183. switch (*code) {
  184. case 's': // short
  185. case 'i': // int
  186. case 'l': // long
  187. case 'q': // long long
  188. _type = RLMPropertyTypeInt;
  189. return YES;
  190. case 'f':
  191. _type = RLMPropertyTypeFloat;
  192. return YES;
  193. case 'd':
  194. _type = RLMPropertyTypeDouble;
  195. return YES;
  196. case 'c': // BOOL is stored as char - since rlm has no char type this is ok
  197. case 'B':
  198. _type = RLMPropertyTypeBool;
  199. return YES;
  200. case '@':
  201. break;
  202. default:
  203. return NO;
  204. }
  205. _optional = true;
  206. static const char arrayPrefix[] = "@\"RLMArray<";
  207. static const int arrayPrefixLen = sizeof(arrayPrefix) - 1;
  208. static const char setPrefix[] = "@\"RLMSet<";
  209. static const int setPrefixLen = sizeof(setPrefix) - 1;
  210. static const char dictionaryPrefix[] = "@\"RLMDictionary<";
  211. static const int dictionaryPrefixLen = sizeof(dictionaryPrefix) - 1;
  212. static const char numberPrefix[] = "@\"NSNumber<";
  213. static const int numberPrefixLen = sizeof(numberPrefix) - 1;
  214. static const char linkingObjectsPrefix[] = "@\"RLMLinkingObjects";
  215. static const int linkingObjectsPrefixLen = sizeof(linkingObjectsPrefix) - 1;
  216. _array = strncmp(code, arrayPrefix, arrayPrefixLen) == 0;
  217. _set = strncmp(code, setPrefix, setPrefixLen) == 0;
  218. _dictionary = strncmp(code, dictionaryPrefix, dictionaryPrefixLen) == 0;
  219. if (strcmp(code, "@\"NSString\"") == 0) {
  220. _type = RLMPropertyTypeString;
  221. }
  222. else if (strcmp(code, "@\"NSDate\"") == 0) {
  223. _type = RLMPropertyTypeDate;
  224. }
  225. else if (strcmp(code, "@\"NSData\"") == 0) {
  226. _type = RLMPropertyTypeData;
  227. }
  228. else if (strcmp(code, "@\"RLMDecimal128\"") == 0) {
  229. _type = RLMPropertyTypeDecimal128;
  230. }
  231. else if (strcmp(code, "@\"RLMObjectId\"") == 0) {
  232. _type = RLMPropertyTypeObjectId;
  233. }
  234. else if (strcmp(code, "@\"NSUUID\"") == 0) {
  235. _type = RLMPropertyTypeUUID;
  236. }
  237. else if (strcmp(code, "@\"<RLMValue>\"") == 0) {
  238. _type = RLMPropertyTypeAny;
  239. // Mixed can represent a null type but can't explicitly be an optional type.
  240. _optional = false;
  241. }
  242. else if (_array || _set || _dictionary) {
  243. size_t prefixLen = 0;
  244. NSString *collectionName;
  245. if (_array) {
  246. prefixLen = arrayPrefixLen;
  247. collectionName = @"RLMArray";
  248. }
  249. else if (_set) {
  250. prefixLen = setPrefixLen;
  251. collectionName = @"RLMSet";
  252. }
  253. else if (_dictionary) {
  254. // get the type, by working backward from RLMDictionary<Key, Type>
  255. size_t typeLen = 0;
  256. size_t codeSize = strlen(code);
  257. for (size_t i = codeSize; i > 0; i--) {
  258. if (code[i] == '>' && i != (codeSize-2)) { // -2 means we skip the first time we see '>'
  259. typeLen = i;
  260. break;
  261. }
  262. }
  263. prefixLen = typeLen+size_t(2); // +2 start at the type name
  264. collectionName = @"RLMDictionary";
  265. // Get the key type
  266. if (strstr(code + dictionaryPrefixLen, "RLMString><") != NULL) {
  267. _dictionaryKeyType = RLMPropertyTypeString;
  268. }
  269. }
  270. if (auto type = typeFromProtocolString(code + prefixLen)) {
  271. _type = *type;
  272. return YES;
  273. }
  274. // get object class from type string - @"RLMSomeCollection<objectClassName>"
  275. _objectClassName = [[NSString alloc] initWithBytes:code + prefixLen
  276. length:strlen(code + prefixLen) - 2 // drop trailing >"
  277. encoding:NSUTF8StringEncoding];
  278. if ([RLMSchema classForString:_objectClassName]) {
  279. // Dictionaries require object types to be nullable. This is due to
  280. // the fact that if you delete a realm object that exists in a dictionary
  281. // the key should stay present but the value should be null.
  282. _optional = _dictionary;
  283. _type = RLMPropertyTypeObject;
  284. return YES;
  285. }
  286. @throw RLMException(@"Property '%@' is of type '%@<%@>' which is not a supported %@ object type. "
  287. @"%@ can only contain instances of RLMObject subclasses. "
  288. @"See https://www.mongodb.com/docs/realm/sdk/swift/fundamentals/relationships/#to-many-relationship "
  289. @"for more information.", _name, collectionName, _objectClassName, collectionName, collectionName);
  290. }
  291. else if (strncmp(code, numberPrefix, numberPrefixLen) == 0) {
  292. auto type = typeFromProtocolString(code + numberPrefixLen);
  293. if (type && (*type == RLMPropertyTypeInt || *type == RLMPropertyTypeFloat || *type == RLMPropertyTypeDouble || *type == RLMPropertyTypeBool)) {
  294. _type = *type;
  295. return YES;
  296. }
  297. @throw RLMException(@"Property '%@' is of type %s which is not a supported NSNumber object type. "
  298. @"NSNumbers can only be RLMInt, RLMFloat, RLMDouble, and RLMBool at the moment. "
  299. @"See https://www.mongodb.com/docs/realm/sdk/swift/data-types/supported-property-types/ "
  300. @"for more information.", _name, code + 1);
  301. }
  302. else if (strncmp(code, linkingObjectsPrefix, linkingObjectsPrefixLen) == 0 &&
  303. (code[linkingObjectsPrefixLen] == '"' || code[linkingObjectsPrefixLen] == '<')) {
  304. _type = RLMPropertyTypeLinkingObjects;
  305. _optional = false;
  306. _array = true;
  307. if (!_objectClassName || !_linkOriginPropertyName) {
  308. @throw RLMException(@"Property '%@' is of type RLMLinkingObjects but +linkingObjectsProperties did not specify the class "
  309. "or property that is the origin of the link.", _name);
  310. }
  311. // If the property was declared with a protocol indicating the contained type, validate that it matches
  312. // the class from the dictionary returned by +linkingObjectsProperties.
  313. if (code[linkingObjectsPrefixLen] == '<') {
  314. NSString *classNameFromProtocol = [[NSString alloc] initWithBytes:code + linkingObjectsPrefixLen + 1
  315. length:strlen(code + linkingObjectsPrefixLen) - 3 // drop trailing >"
  316. encoding:NSUTF8StringEncoding];
  317. if (![_objectClassName isEqualToString:classNameFromProtocol]) {
  318. @throw RLMException(@"Property '%@' was declared with type RLMLinkingObjects<%@>, but a conflicting "
  319. "class name of '%@' was returned by +linkingObjectsProperties.", _name,
  320. classNameFromProtocol, _objectClassName);
  321. }
  322. }
  323. }
  324. else if (strcmp(code, "@\"NSNumber\"") == 0) {
  325. @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: NSNumber<RLMInt>.", _name);
  326. }
  327. else if (strcmp(code, "@\"RLMArray\"") == 0) {
  328. @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMArray<Person>.", _name);
  329. }
  330. else if (strcmp(code, "@\"RLMSet\"") == 0) {
  331. @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMSet<Person>.", _name);
  332. }
  333. else if (strcmp(code, "@\"RLMDictionary\"") == 0) {
  334. @throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMDictionary<NSString *, Person *><RLMString, Person>.", _name);
  335. }
  336. else {
  337. NSString *className;
  338. Class cls = nil;
  339. if (code[1] == '\0') {
  340. className = @"id";
  341. }
  342. else {
  343. // for objects strip the quotes and @
  344. className = [rawType substringWithRange:NSMakeRange(2, rawType.length-3)];
  345. cls = [RLMSchema classForString:className];
  346. }
  347. if (!cls) {
  348. @throw RLMException(@"Property '%@' is declared as '%@', which is not a supported RLMObject property type. "
  349. @"All properties must be primitives, NSString, NSDate, NSData, NSNumber, RLMArray, RLMSet, "
  350. @"RLMDictionary, RLMLinkingObjects, RLMDecimal128, RLMObjectId, or subclasses of RLMObject. "
  351. @"See https://www.mongodb.com/docs/realm-legacy/docs/objc/latest/api/Classes/RLMObject.html "
  352. @"for more information.", _name, className);
  353. }
  354. _type = RLMPropertyTypeObject;
  355. _optional = true;
  356. _objectClassName = [cls className] ?: className;
  357. }
  358. return YES;
  359. }
  360. - (void)parseObjcProperty:(objc_property_t)property
  361. readOnly:(bool *)readOnly
  362. computed:(bool *)computed
  363. rawType:(NSString **)rawType {
  364. unsigned int count;
  365. objc_property_attribute_t *attrs = property_copyAttributeList(property, &count);
  366. *computed = true;
  367. for (size_t i = 0; i < count; ++i) {
  368. switch (*attrs[i].name) {
  369. case 'T':
  370. *rawType = @(attrs[i].value);
  371. break;
  372. case 'R':
  373. *readOnly = true;
  374. break;
  375. case 'G':
  376. _getterName = @(attrs[i].value);
  377. break;
  378. case 'S':
  379. _setterName = @(attrs[i].value);
  380. break;
  381. case 'V': // backing ivar name
  382. *computed = false;
  383. break;
  384. case '&':
  385. // retain/assign
  386. break;
  387. case 'C':
  388. // copy
  389. break;
  390. case 'D':
  391. // dynamic
  392. break;
  393. case 'N':
  394. // nonatomic
  395. break;
  396. case 'P':
  397. // GC'able
  398. break;
  399. case 'W':
  400. // weak
  401. break;
  402. default:
  403. break;
  404. }
  405. }
  406. free(attrs);
  407. }
  408. - (instancetype)initSwiftPropertyWithName:(NSString *)name
  409. indexed:(BOOL)indexed
  410. linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor
  411. property:(objc_property_t)property
  412. instance:(RLMObject *)obj {
  413. self = [super init];
  414. if (!self) {
  415. return nil;
  416. }
  417. RLMValidateSwiftPropertyName(name);
  418. _name = name;
  419. _indexed = indexed;
  420. if (linkPropertyDescriptor) {
  421. _objectClassName = [linkPropertyDescriptor.objectClass className];
  422. _linkOriginPropertyName = linkPropertyDescriptor.propertyName;
  423. }
  424. NSString *rawType;
  425. bool readOnly = false;
  426. bool isComputed = false;
  427. [self parseObjcProperty:property readOnly:&readOnly computed:&isComputed rawType:&rawType];
  428. // Swift sometimes doesn't explicitly set the ivar name in the metadata, so check if
  429. // there's an ivar with the same name as the property.
  430. if (!readOnly && isComputed && class_getInstanceVariable([obj class], name.UTF8String)) {
  431. isComputed = false;
  432. }
  433. // Check if there's a storage ivar for a lazy property in this name. We don't honor
  434. // @lazy in managed objects, but allow it for unmanaged objects which are
  435. // subclasses of RLMObject (but not RealmSwift.Object). It's unclear if there's a
  436. // good reason for this difference.
  437. if (!readOnly && isComputed) {
  438. // Xcode 10 and earlier
  439. NSString *backingPropertyName = [NSString stringWithFormat:@"%@.storage", name];
  440. isComputed = !class_getInstanceVariable([obj class], backingPropertyName.UTF8String);
  441. }
  442. if (!readOnly && isComputed) {
  443. // Xcode 11
  444. NSString *backingPropertyName = [NSString stringWithFormat:@"$__lazy_storage_$_%@", name];
  445. isComputed = !class_getInstanceVariable([obj class], backingPropertyName.UTF8String);
  446. }
  447. if (readOnly || isComputed) {
  448. return nil;
  449. }
  450. id propertyValue = [obj valueForKey:_name];
  451. // FIXME: temporarily workaround added since Objective-C generics used in Swift show up as `@`
  452. // * broken starting in Swift 3.0 Xcode 8 b1
  453. // * tested to still be broken in Swift 3.0 Xcode 8 b6
  454. // * if the Realm Objective-C Swift tests pass with this removed, it's been fixed
  455. // * once it has been fixed, remove this entire conditional block (contents included) entirely
  456. // * Bug Report: SR-2031 https://bugs.swift.org/browse/SR-2031
  457. if ([rawType isEqualToString:@"@"]) {
  458. if (propertyValue) {
  459. rawType = [NSString stringWithFormat:@"@\"%@\"", [propertyValue class]];
  460. } else if (linkPropertyDescriptor) {
  461. // we're going to naively assume that the user used the correct type since we can't check it
  462. rawType = @"@\"RLMLinkingObjects\"";
  463. }
  464. }
  465. // convert array / set / dictionary types to objc variant
  466. if ([rawType isEqualToString:@"@\"RLMArray\""]) {
  467. RLMArray *value = propertyValue;
  468. _type = value.type;
  469. _optional = value.optional;
  470. _array = true;
  471. _objectClassName = value.objectClassName;
  472. if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) {
  473. @throw RLMException(@"Property '%@' is of type 'RLMArray<%@>' which is not a supported RLMArray object type. "
  474. @"RLMArrays can only contain instances of RLMObject subclasses. "
  475. @"See https://www.mongodb.com/docs/realm/sdk/swift/fundamentals/relationships/#to-many-relationship "
  476. @"for more information.", _name, _objectClassName);
  477. }
  478. }
  479. else if ([rawType isEqualToString:@"@\"RLMSet\""]) {
  480. RLMSet *value = propertyValue;
  481. _type = value.type;
  482. _optional = value.optional;
  483. _set = true;
  484. _objectClassName = value.objectClassName;
  485. if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) {
  486. @throw RLMException(@"Property '%@' is of type 'RLMSet<%@>' which is not a supported RLMSet object type. "
  487. @"RLMSets can only contain instances of RLMObject subclasses. "
  488. @"See https://www.mongodb.com/docs/realm/sdk/swift/fundamentals/relationships/#to-many-relationship "
  489. @"for more information.", _name, _objectClassName);
  490. }
  491. }
  492. else if ([rawType isEqualToString:@"@\"RLMDictionary\""]) {
  493. RLMDictionary *value = propertyValue;
  494. _type = value.type;
  495. _dictionaryKeyType = value.keyType;
  496. _optional = value.optional;
  497. _dictionary = true;
  498. _objectClassName = value.objectClassName;
  499. if (_type == RLMPropertyTypeObject && ![RLMSchema classForString:_objectClassName]) {
  500. @throw RLMException(@"Property '%@' is of type 'RLMDictionary<KeyType, %@>' which is not a supported RLMDictionary object type. "
  501. @"RLMDictionarys can only contain instances of RLMObject subclasses. "
  502. @"See https://www.mongodb.com/docs/realm/sdk/swift/fundamentals/relationships/#to-many-relationship "
  503. @"for more information.", _name, _objectClassName);
  504. }
  505. }
  506. else if ([rawType isEqualToString:@"@\"NSNumber\""]) {
  507. const char *numberType = [propertyValue objCType];
  508. if (!numberType) {
  509. @throw RLMException(@"Can't persist NSNumber without default value: use a Swift-native number type or provide a default value.");
  510. }
  511. _optional = true;
  512. switch (*numberType) {
  513. case 'i': case 'l': case 'q':
  514. _type = RLMPropertyTypeInt;
  515. break;
  516. case 'f':
  517. _type = RLMPropertyTypeFloat;
  518. break;
  519. case 'd':
  520. _type = RLMPropertyTypeDouble;
  521. break;
  522. case 'B': case 'c':
  523. _type = RLMPropertyTypeBool;
  524. break;
  525. default:
  526. @throw RLMException(@"Can't persist NSNumber of type '%s': only integers, floats, doubles, and bools are currently supported.", numberType);
  527. }
  528. }
  529. else if (![self setTypeFromRawType:rawType]) {
  530. @throw RLMException(@"Can't persist property '%@' with incompatible type. "
  531. "Add to Object.ignoredProperties() class method to ignore.",
  532. self.name);
  533. }
  534. if ([rawType isEqualToString:@"c"]) {
  535. // Check if it's a BOOL or Int8 by trying to set it to 2 and seeing if
  536. // it actually sets it to 1.
  537. [obj setValue:@2 forKey:name];
  538. NSNumber *value = [obj valueForKey:name];
  539. _type = value.intValue == 2 ? RLMPropertyTypeInt : RLMPropertyTypeBool;
  540. }
  541. // update getter/setter names
  542. [self updateAccessors];
  543. return self;
  544. }
  545. - (instancetype)initWithName:(NSString *)name
  546. indexed:(BOOL)indexed
  547. linkPropertyDescriptor:(RLMPropertyDescriptor *)linkPropertyDescriptor
  548. property:(objc_property_t)property
  549. {
  550. self = [super init];
  551. if (!self) {
  552. return nil;
  553. }
  554. _name = name;
  555. _indexed = indexed;
  556. if (linkPropertyDescriptor) {
  557. _objectClassName = [linkPropertyDescriptor.objectClass className];
  558. _linkOriginPropertyName = linkPropertyDescriptor.propertyName;
  559. }
  560. NSString *rawType;
  561. bool isReadOnly = false;
  562. bool isComputed = false;
  563. [self parseObjcProperty:property readOnly:&isReadOnly computed:&isComputed rawType:&rawType];
  564. bool shouldBeTreatedAsComputedProperty = rawTypeShouldBeTreatedAsComputedProperty(rawType);
  565. if ((isReadOnly || isComputed) && !shouldBeTreatedAsComputedProperty) {
  566. return nil;
  567. }
  568. if (![self setTypeFromRawType:rawType]) {
  569. @throw RLMException(@"Can't persist property '%@' with incompatible type. "
  570. "Add to ignoredPropertyNames: method to ignore.", self.name);
  571. }
  572. if (!isReadOnly && shouldBeTreatedAsComputedProperty) {
  573. @throw RLMException(@"Property '%@' must be declared as readonly as %@ properties cannot be written to.",
  574. self.name, RLMTypeToString(_type));
  575. }
  576. // update getter/setter names
  577. [self updateAccessors];
  578. return self;
  579. }
  580. - (id)copyWithZone:(NSZone *)zone {
  581. RLMProperty *prop = [[RLMProperty allocWithZone:zone] init];
  582. prop->_name = _name;
  583. prop->_columnName = _columnName;
  584. prop->_type = _type;
  585. prop->_objectClassName = _objectClassName;
  586. prop->_array = _array;
  587. prop->_set = _set;
  588. prop->_dictionary = _dictionary;
  589. prop->_dictionaryKeyType = _dictionaryKeyType;
  590. prop->_indexed = _indexed;
  591. prop->_getterName = _getterName;
  592. prop->_setterName = _setterName;
  593. prop->_getterSel = _getterSel;
  594. prop->_setterSel = _setterSel;
  595. prop->_isPrimary = _isPrimary;
  596. prop->_swiftAccessor = _swiftAccessor;
  597. prop->_swiftIvar = _swiftIvar;
  598. prop->_optional = _optional;
  599. prop->_linkOriginPropertyName = _linkOriginPropertyName;
  600. return prop;
  601. }
  602. - (RLMProperty *)copyWithNewName:(NSString *)name {
  603. RLMProperty *prop = [self copy];
  604. prop.name = name;
  605. return prop;
  606. }
  607. - (BOOL)isEqual:(id)object {
  608. if (![object isKindOfClass:[RLMProperty class]]) {
  609. return NO;
  610. }
  611. return [self isEqualToProperty:object];
  612. }
  613. - (BOOL)isEqualToProperty:(RLMProperty *)property {
  614. return _type == property->_type
  615. && _indexed == property->_indexed
  616. && _isPrimary == property->_isPrimary
  617. && _optional == property->_optional
  618. && [_name isEqualToString:property->_name]
  619. && (_objectClassName == property->_objectClassName || [_objectClassName isEqualToString:property->_objectClassName])
  620. && (_linkOriginPropertyName == property->_linkOriginPropertyName ||
  621. [_linkOriginPropertyName isEqualToString:property->_linkOriginPropertyName]);
  622. }
  623. - (BOOL)collection {
  624. return self.set || self.array || self.dictionary;
  625. }
  626. - (NSString *)description {
  627. NSString *objectClassName = @"";
  628. if (self.type == RLMPropertyTypeObject || self.type == RLMPropertyTypeLinkingObjects) {
  629. objectClassName = [NSString stringWithFormat:
  630. @"\tobjectClassName = %@;\n"
  631. @"\tlinkOriginPropertyName = %@;\n",
  632. self.objectClassName, self.linkOriginPropertyName];
  633. }
  634. return [NSString stringWithFormat:
  635. @"%@ {\n"
  636. "\ttype = %@;\n"
  637. "%@"
  638. "\tcolumnName = %@;\n"
  639. "\tindexed = %@;\n"
  640. "\tisPrimary = %@;\n"
  641. "\tarray = %@;\n"
  642. "\tset = %@;\n"
  643. "\tdictionary = %@;\n"
  644. "\toptional = %@;\n"
  645. "}",
  646. self.name, RLMTypeToString(self.type),
  647. objectClassName,
  648. self.columnName,
  649. self.indexed ? @"YES" : @"NO",
  650. self.isPrimary ? @"YES" : @"NO",
  651. self.array ? @"YES" : @"NO",
  652. self.set ? @"YES" : @"NO",
  653. self.dictionary ? @"YES" : @"NO",
  654. self.optional ? @"YES" : @"NO"];
  655. }
  656. - (NSString *)columnName {
  657. return _columnName ?: _name;
  658. }
  659. - (realm::Property)objectStoreCopy:(RLMSchema *)schema {
  660. realm::Property p;
  661. p.name = self.columnName.UTF8String;
  662. if (_columnName) {
  663. p.public_name = _name.UTF8String;
  664. }
  665. if (_objectClassName) {
  666. RLMObjectSchema *targetSchema = schema[_objectClassName];
  667. p.object_type = (targetSchema.objectName ?: _objectClassName).UTF8String;
  668. if (_linkOriginPropertyName) {
  669. p.link_origin_property_name = (targetSchema[_linkOriginPropertyName].columnName ?: _linkOriginPropertyName).UTF8String;
  670. }
  671. }
  672. p.is_indexed = static_cast<bool>(_indexed);
  673. p.type = static_cast<realm::PropertyType>(_type);
  674. if (_array) {
  675. p.type |= realm::PropertyType::Array;
  676. }
  677. if (_set) {
  678. p.type |= realm::PropertyType::Set;
  679. }
  680. if (_dictionary) {
  681. p.type |= realm::PropertyType::Dictionary;
  682. }
  683. if (_optional || p.type == realm::PropertyType::Mixed) {
  684. p.type |= realm::PropertyType::Nullable;
  685. }
  686. return p;
  687. }
  688. - (NSString *)typeName {
  689. if (!self.collection) {
  690. return RLMTypeToString(_type);
  691. }
  692. NSString *collectionName;
  693. if (_swiftAccessor) {
  694. collectionName = _array ? @"List" :
  695. _set ? @"MutableSet" :
  696. @"Map";
  697. }
  698. else {
  699. collectionName = _array ? @"RLMArray" :
  700. _set ? @"RLMSet" :
  701. @"RLMDictionary";
  702. }
  703. return [NSString stringWithFormat:@"%@<%@>", collectionName, RLMTypeToString(_type)];
  704. }
  705. @end
  706. @implementation RLMPropertyDescriptor
  707. + (instancetype)descriptorWithClass:(Class)objectClass propertyName:(NSString *)propertyName
  708. {
  709. RLMPropertyDescriptor *descriptor = [[RLMPropertyDescriptor alloc] init];
  710. descriptor->_objectClass = objectClass;
  711. descriptor->_propertyName = propertyName;
  712. return descriptor;
  713. }
  714. @end