RLMSchema.mm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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 "RLMSchema_Private.hpp"
  19. #import "RLMAccessor.h"
  20. #import "RLMObjectBase_Private.h"
  21. #import "RLMObject_Private.hpp"
  22. #import "RLMObjectSchema_Private.hpp"
  23. #import "RLMProperty_Private.h"
  24. #import "RLMRealm_Private.hpp"
  25. #import "RLMSwiftSupport.h"
  26. #import "RLMUtil.hpp"
  27. #import <realm/group.hpp>
  28. #import <realm/object-store/object_schema.hpp>
  29. #import <realm/object-store/object_store.hpp>
  30. #import <realm/object-store/schema.hpp>
  31. #import <realm/util/scope_exit.hpp>
  32. #import <mutex>
  33. #import <objc/runtime.h>
  34. using namespace realm;
  35. const uint64_t RLMNotVersioned = realm::ObjectStore::NotVersioned;
  36. // RLMSchema private properties
  37. @interface RLMSchema ()
  38. @property (nonatomic, readwrite) NSMutableDictionary *objectSchemaByName;
  39. @end
  40. // Private RLMSchema subclass that skips class registration on lookup
  41. @interface RLMPrivateSchema : RLMSchema
  42. @end
  43. @implementation RLMPrivateSchema
  44. - (RLMObjectSchema *)schemaForClassName:(NSString *)className {
  45. return self.objectSchemaByName[className];
  46. }
  47. - (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className {
  48. return [self schemaForClassName:className];
  49. }
  50. @end
  51. static RLMSchema *s_sharedSchema = [[RLMSchema alloc] init];
  52. static NSMutableDictionary *s_localNameToClass = [[NSMutableDictionary alloc] init];
  53. static RLMSchema *s_privateSharedSchema = [[RLMPrivateSchema alloc] init];
  54. static enum class SharedSchemaState {
  55. Uninitialized,
  56. Initializing,
  57. Initialized
  58. } s_sharedSchemaState = SharedSchemaState::Uninitialized;
  59. @implementation RLMSchema {
  60. NSArray *_objectSchema;
  61. realm::Schema _objectStoreSchema;
  62. }
  63. static void createAccessors(RLMObjectSchema *objectSchema) {
  64. constexpr const size_t bufferSize
  65. = sizeof("RLM:Managed ") // includes spot for null terminator
  66. + std::numeric_limits<unsigned long long>::digits10
  67. + realm::Group::max_table_name_length;
  68. char className[bufferSize] = "RLM:Managed ";
  69. char *const start = className + strlen(className);
  70. static unsigned long long count = 0;
  71. snprintf(start, bufferSize - strlen(className),
  72. "%llu %s", count++, objectSchema.className.UTF8String);
  73. objectSchema.accessorClass = RLMManagedAccessorClassForObjectClass(objectSchema.objectClass, objectSchema, className);
  74. objectSchema.unmanagedClass = RLMUnmanagedAccessorClassForObjectClass(objectSchema.objectClass, objectSchema);
  75. }
  76. void RLMSchemaEnsureAccessorsCreated(RLMSchema *schema) {
  77. for (RLMObjectSchema *objectSchema in schema.objectSchema) {
  78. if (objectSchema.accessorClass == objectSchema.objectClass) {
  79. // Locking inside the loop to optimize for the common case at
  80. // the expense of worse perf in the rare scenario where this is
  81. // actually needed.
  82. @synchronized(s_localNameToClass) {
  83. createAccessors(objectSchema);
  84. }
  85. }
  86. }
  87. }
  88. // Caller must @synchronize on s_localNameToClass
  89. static RLMObjectSchema *registerClass(Class cls) {
  90. if (RLMObjectSchema *schema = s_privateSharedSchema[[cls className]]) {
  91. return schema;
  92. }
  93. auto prevState = s_sharedSchemaState;
  94. s_sharedSchemaState = SharedSchemaState::Initializing;
  95. RLMObjectSchema *schema;
  96. {
  97. util::ScopeExit cleanup([&]() noexcept {
  98. s_sharedSchemaState = prevState;
  99. });
  100. schema = [RLMObjectSchema schemaForObjectClass:cls];
  101. }
  102. createAccessors(schema);
  103. // override sharedSchema class methods for performance
  104. RLMReplaceSharedSchemaMethod(cls, schema);
  105. s_privateSharedSchema.objectSchemaByName[schema.className] = schema;
  106. if ([cls shouldIncludeInDefaultSchema] && prevState != SharedSchemaState::Initialized) {
  107. s_sharedSchema.objectSchemaByName[schema.className] = schema;
  108. }
  109. return schema;
  110. }
  111. // Caller must @synchronize on s_localNameToClass
  112. static void RLMRegisterClassLocalNames(Class *classes, NSUInteger count) {
  113. for (NSUInteger i = 0; i < count; i++) {
  114. Class cls = classes[i];
  115. if (!RLMIsObjectSubclass(cls)) {
  116. continue;
  117. }
  118. if ([cls _realmIgnoreClass]) {
  119. continue;
  120. }
  121. NSString *className = NSStringFromClass(cls);
  122. if ([className hasPrefix:@"RLM:"] || [className hasPrefix:@"NSKVONotifying"]) {
  123. continue;
  124. }
  125. if ([RLMSwiftSupport isSwiftClassName:className]) {
  126. className = [RLMSwiftSupport demangleClassName:className];
  127. }
  128. // NSStringFromClass demangles the names for top-level Swift classes
  129. // but not for nested classes. _T indicates it's a Swift symbol, t
  130. // indicates it's a type, and C indicates it's a class.
  131. else if ([className hasPrefix:@"_TtC"]) {
  132. @throw RLMException(@"RLMObject subclasses cannot be nested within other declarations. Please move %@ to global scope.", className);
  133. }
  134. if (Class existingClass = s_localNameToClass[className]) {
  135. if (existingClass != cls) {
  136. @throw RLMException(@"RLMObject subclasses with the same name cannot be included twice in the same target. "
  137. @"Please make sure '%@' is only linked once to your current target.", className);
  138. }
  139. continue;
  140. }
  141. s_localNameToClass[className] = cls;
  142. RLMReplaceClassNameMethod(cls, className);
  143. }
  144. }
  145. - (instancetype)init {
  146. self = [super init];
  147. if (self) {
  148. _objectSchemaByName = [[NSMutableDictionary alloc] init];
  149. }
  150. return self;
  151. }
  152. - (NSArray *)objectSchema {
  153. if (!_objectSchema) {
  154. _objectSchema = [_objectSchemaByName allValues];
  155. }
  156. return _objectSchema;
  157. }
  158. - (void)setObjectSchema:(NSArray *)objectSchema {
  159. _objectSchema = objectSchema;
  160. _objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:objectSchema.count];
  161. for (RLMObjectSchema *object in objectSchema) {
  162. [_objectSchemaByName setObject:object forKey:object.className];
  163. }
  164. }
  165. - (RLMObjectSchema *)schemaForClassName:(NSString *)className {
  166. if (RLMObjectSchema *schema = _objectSchemaByName[className]) {
  167. return schema; // fast path for already-initialized schemas
  168. } else if (Class cls = [RLMSchema classForString:className]) {
  169. [cls sharedSchema]; // initialize the schema
  170. return _objectSchemaByName[className]; // try again
  171. } else {
  172. return nil;
  173. }
  174. }
  175. - (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className {
  176. RLMObjectSchema *schema = [self schemaForClassName:className];
  177. if (!schema) {
  178. @throw RLMException(@"Object type '%@' not managed by the Realm", className);
  179. }
  180. return schema;
  181. }
  182. + (instancetype)schemaWithObjectClasses:(NSArray *)classes {
  183. NSUInteger count = classes.count;
  184. auto classArray = std::make_unique<__unsafe_unretained Class[]>(count);
  185. [classes getObjects:classArray.get() range:NSMakeRange(0, count)];
  186. RLMSchema *schema = [[self alloc] init];
  187. @synchronized(s_localNameToClass) {
  188. RLMRegisterClassLocalNames(classArray.get(), count);
  189. schema->_objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:count];
  190. for (Class cls in classes) {
  191. if (!RLMIsObjectSubclass(cls)) {
  192. @throw RLMException(@"Can't add non-Object type '%@' to a schema.", cls);
  193. }
  194. schema->_objectSchemaByName[[cls className]] = registerClass(cls);
  195. }
  196. }
  197. NSMutableArray *errors = [NSMutableArray new];
  198. // Verify that all of the targets of links are included in the class list
  199. [schema->_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(id, RLMObjectSchema *objectSchema, BOOL *) {
  200. for (RLMProperty *prop in objectSchema.properties) {
  201. if (prop.type != RLMPropertyTypeObject) {
  202. continue;
  203. }
  204. if (!schema->_objectSchemaByName[prop.objectClassName]) {
  205. [errors addObject:[NSString stringWithFormat:@"- '%@.%@' links to class '%@', which is missing from the list of classes managed by the Realm", objectSchema.className, prop.name, prop.objectClassName]];
  206. }
  207. }
  208. }];
  209. if (errors.count) {
  210. @throw RLMException(@"Invalid class subset list:\n%@", [errors componentsJoinedByString:@"\n"]);
  211. }
  212. return schema;
  213. }
  214. + (RLMObjectSchema *)sharedSchemaForClass:(Class)cls {
  215. @synchronized(s_localNameToClass) {
  216. // We create instances of Swift objects during schema init, and they
  217. // obviously need to not also try to initialize the schema
  218. if (s_sharedSchemaState == SharedSchemaState::Initializing) {
  219. return nil;
  220. }
  221. // Don't register the base classes in the schema even if someone calls
  222. // sharedSchema on them directly
  223. if (cls == [RLMObjectBase class] || class_getSuperclass(cls) == [RLMObjectBase class]) {
  224. return nil;
  225. }
  226. RLMRegisterClassLocalNames(&cls, 1);
  227. RLMObjectSchema *objectSchema = registerClass(cls);
  228. [cls initializeLinkedObjectSchemas];
  229. return objectSchema;
  230. }
  231. }
  232. + (instancetype)partialSharedSchema {
  233. return s_sharedSchema;
  234. }
  235. + (instancetype)partialPrivateSharedSchema {
  236. return s_privateSharedSchema;
  237. }
  238. // schema based on runtime objects
  239. + (instancetype)sharedSchema {
  240. @synchronized(s_localNameToClass) {
  241. // We replace this method with one which just returns s_sharedSchema
  242. // once initialization is complete, but we still need to check if it's
  243. // already complete because it may have been done by another thread
  244. // while we were waiting for the lock
  245. if (s_sharedSchemaState == SharedSchemaState::Initialized) {
  246. return s_sharedSchema;
  247. }
  248. if (s_sharedSchemaState == SharedSchemaState::Initializing) {
  249. @throw RLMException(@"Illegal recursive call of +[%@ %@]. Note: Properties of Swift `Object` classes must not be prepopulated with queried results from a Realm.", self, NSStringFromSelector(_cmd));
  250. }
  251. s_sharedSchemaState = SharedSchemaState::Initializing;
  252. try {
  253. // Make sure we've discovered all classes
  254. {
  255. unsigned int numClasses;
  256. using malloc_ptr = std::unique_ptr<__unsafe_unretained Class[], decltype(&free)>;
  257. malloc_ptr classes(objc_copyClassList(&numClasses), &free);
  258. RLMRegisterClassLocalNames(classes.get(), numClasses);
  259. }
  260. [s_localNameToClass enumerateKeysAndObjectsUsingBlock:^(NSString *, Class cls, BOOL *) {
  261. registerClass(cls);
  262. }];
  263. }
  264. catch (...) {
  265. s_sharedSchemaState = SharedSchemaState::Uninitialized;
  266. throw;
  267. }
  268. // Replace this method with one that doesn't need to acquire a lock
  269. Class metaClass = objc_getMetaClass(class_getName(self));
  270. IMP imp = imp_implementationWithBlock(^{ return s_sharedSchema; });
  271. class_replaceMethod(metaClass, @selector(sharedSchema), imp, "@@:");
  272. s_sharedSchemaState = SharedSchemaState::Initialized;
  273. }
  274. return s_sharedSchema;
  275. }
  276. // schema based on tables in a realm
  277. + (instancetype)dynamicSchemaFromObjectStoreSchema:(Schema const&)objectStoreSchema {
  278. // cache descriptors for all subclasses of RLMObject
  279. NSMutableArray *schemaArray = [NSMutableArray arrayWithCapacity:objectStoreSchema.size()];
  280. for (auto &objectSchema : objectStoreSchema) {
  281. RLMObjectSchema *schema = [RLMObjectSchema objectSchemaForObjectStoreSchema:objectSchema];
  282. [schemaArray addObject:schema];
  283. }
  284. // set class array and mapping
  285. RLMSchema *schema = [RLMSchema new];
  286. schema.objectSchema = schemaArray;
  287. return schema;
  288. }
  289. + (Class)classForString:(NSString *)className {
  290. if (Class cls = s_localNameToClass[className]) {
  291. return cls;
  292. }
  293. if (Class cls = NSClassFromString(className)) {
  294. return RLMIsObjectSubclass(cls) ? cls : nil;
  295. }
  296. // className might be the local name of a Swift class we haven't registered
  297. // yet, so scan them all then recheck
  298. {
  299. unsigned int numClasses;
  300. std::unique_ptr<__unsafe_unretained Class[], decltype(&free)> classes(objc_copyClassList(&numClasses), &free);
  301. RLMRegisterClassLocalNames(classes.get(), numClasses);
  302. }
  303. return s_localNameToClass[className];
  304. }
  305. - (id)copyWithZone:(NSZone *)zone {
  306. RLMSchema *schema = [[RLMSchema allocWithZone:zone] init];
  307. schema->_objectSchemaByName = [[NSMutableDictionary allocWithZone:zone]
  308. initWithDictionary:_objectSchemaByName copyItems:YES];
  309. return schema;
  310. }
  311. - (BOOL)isEqualToSchema:(RLMSchema *)schema {
  312. if (_objectSchemaByName.count != schema->_objectSchemaByName.count) {
  313. return NO;
  314. }
  315. __block BOOL matches = YES;
  316. [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RLMObjectSchema *objectSchema, BOOL *stop) {
  317. if (![schema->_objectSchemaByName[name] isEqualToObjectSchema:objectSchema]) {
  318. *stop = YES;
  319. matches = NO;
  320. }
  321. }];
  322. return matches;
  323. }
  324. - (NSString *)description {
  325. NSMutableString *objectSchemaString = [NSMutableString string];
  326. NSArray *sort = @[[NSSortDescriptor sortDescriptorWithKey:@"className" ascending:YES]];
  327. for (RLMObjectSchema *objectSchema in [self.objectSchema sortedArrayUsingDescriptors:sort]) {
  328. [objectSchemaString appendFormat:@"\t%@\n",
  329. [objectSchema.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
  330. }
  331. return [NSString stringWithFormat:@"Schema {\n%@}", objectSchemaString];
  332. }
  333. - (Schema)objectStoreCopy {
  334. if (_objectStoreSchema.size() == 0) {
  335. std::vector<realm::ObjectSchema> schema;
  336. schema.reserve(_objectSchemaByName.count);
  337. [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:[&](NSString *, RLMObjectSchema *objectSchema, BOOL *) {
  338. schema.push_back([objectSchema objectStoreCopy:self]);
  339. }];
  340. // Having both obj-c and Swift classes for the same tables results in
  341. // duplicate ObjectSchemas that we need to filter out
  342. std::sort(begin(schema), end(schema), [](auto&& a, auto&& b) { return a.name < b.name; });
  343. schema.erase(std::unique(begin(schema), end(schema), [](auto&& a, auto&& b) {
  344. if (a.name == b.name) {
  345. // If we make _realmObjectName public this needs to be turned into an exception
  346. REALM_ASSERT_DEBUG(a.persisted_properties == b.persisted_properties);
  347. return true;
  348. }
  349. return false;
  350. }), end(schema));
  351. _objectStoreSchema = std::move(schema);
  352. }
  353. return _objectStoreSchema;
  354. }
  355. @end