RLMSchema.mm 14 KB

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