RLMSchema.mm 14 KB

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