1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2014 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
19 #import "RLMSchema_Private.h"
21 #import "RLMAccessor.h"
22 #import "RLMObjectBase_Private.h"
23 #import "RLMObject_Private.hpp"
24 #import "RLMObjectSchema_Private.hpp"
25 #import "RLMProperty_Private.h"
26 #import "RLMRealm_Private.hpp"
27 #import "RLMSwiftSupport.h"
30 #import "object_store.hpp"
33 #import <realm/group.hpp>
35 #import <objc/runtime.h>
38 using namespace realm;
40 const uint64_t RLMNotVersioned = realm::ObjectStore::NotVersioned;
42 // RLMSchema private properties
43 @interface RLMSchema ()
44 @property (nonatomic, readwrite) NSMutableDictionary *objectSchemaByName;
47 // Private RLMSchema subclass that skips class registration on lookup
48 @interface RLMPrivateSchema : RLMSchema
50 @implementation RLMPrivateSchema
51 - (RLMObjectSchema *)schemaForClassName:(NSString *)className {
52 return self.objectSchemaByName[className];
55 - (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className {
56 return [self schemaForClassName:className];
60 static RLMSchema *s_sharedSchema = [[RLMSchema alloc] init];
61 static NSMutableDictionary *s_localNameToClass = [[NSMutableDictionary alloc] init];
62 static RLMSchema *s_privateSharedSchema = [[RLMPrivateSchema alloc] init];
64 static enum class SharedSchemaState {
68 } s_sharedSchemaState = SharedSchemaState::Uninitialized;
70 @implementation RLMSchema {
71 NSArray *_objectSchema;
72 realm::Schema _objectStoreSchema;
75 // Caller must @synchronize on s_localNameToClass
76 static RLMObjectSchema *RLMRegisterClass(Class cls) {
77 if (RLMObjectSchema *schema = s_privateSharedSchema[[cls className]]) {
81 auto prevState = s_sharedSchemaState;
82 s_sharedSchemaState = SharedSchemaState::Initializing;
83 RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:cls];
84 s_sharedSchemaState = prevState;
86 // set unmanaged class on shared shema for unmanaged object creation
87 schema.unmanagedClass = RLMUnmanagedAccessorClassForObjectClass(schema.objectClass, schema);
89 // override sharedSchema class methods for performance
90 RLMReplaceSharedSchemaMethod(cls, schema);
92 s_privateSharedSchema.objectSchemaByName[schema.className] = schema;
93 if ([cls shouldIncludeInDefaultSchema] && prevState != SharedSchemaState::Initialized) {
94 s_sharedSchema.objectSchemaByName[schema.className] = schema;
100 // Caller must @synchronize on s_localNameToClass
101 static void RLMRegisterClassLocalNames(Class *classes, NSUInteger count) {
102 for (NSUInteger i = 0; i < count; i++) {
103 Class cls = classes[i];
104 if (!RLMIsObjectSubclass(cls)) {
108 NSString *className = NSStringFromClass(cls);
109 if ([className hasPrefix:@"RLM:"] || [className hasPrefix:@"NSKVONotifying"]) {
113 if ([RLMSwiftSupport isSwiftClassName:className]) {
114 className = [RLMSwiftSupport demangleClassName:className];
116 // NSStringFromClass demangles the names for top-level Swift classes
117 // but not for nested classes. _T indicates it's a Swift symbol, t
118 // indicates it's a type, and C indicates it's a class.
119 else if ([className hasPrefix:@"_TtC"]) {
120 @throw RLMException(@"RLMObject subclasses cannot be nested within other declarations. Please move %@ to global scope.", className);
123 if (Class existingClass = s_localNameToClass[className]) {
124 if (existingClass != cls) {
125 @throw RLMException(@"RLMObject subclasses with the same name cannot be included twice in the same target. "
126 @"Please make sure '%@' is only linked once to your current target.", className);
131 s_localNameToClass[className] = cls;
132 RLMReplaceClassNameMethod(cls, className);
136 - (instancetype)init {
139 _objectSchemaByName = [[NSMutableDictionary alloc] init];
144 - (NSArray *)objectSchema {
145 if (!_objectSchema) {
146 _objectSchema = [_objectSchemaByName allValues];
148 return _objectSchema;
151 - (void)setObjectSchema:(NSArray *)objectSchema {
152 _objectSchema = objectSchema;
153 _objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:objectSchema.count];
154 for (RLMObjectSchema *object in objectSchema) {
155 [_objectSchemaByName setObject:object forKey:object.className];
159 - (RLMObjectSchema *)schemaForClassName:(NSString *)className {
160 if (RLMObjectSchema *schema = _objectSchemaByName[className]) {
161 return schema; // fast path for already-initialized schemas
162 } else if (Class cls = [RLMSchema classForString:className]) {
163 [cls sharedSchema]; // initialize the schema
164 return _objectSchemaByName[className]; // try again
170 - (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className {
171 RLMObjectSchema *schema = [self schemaForClassName:className];
173 @throw RLMException(@"Object type '%@' not managed by the Realm", className);
178 + (instancetype)schemaWithObjectClasses:(NSArray *)classes {
179 NSUInteger count = classes.count;
180 auto classArray = std::make_unique<__unsafe_unretained Class[]>(count);
181 [classes getObjects:classArray.get() range:NSMakeRange(0, count)];
183 RLMSchema *schema = [[self alloc] init];
184 @synchronized(s_localNameToClass) {
185 RLMRegisterClassLocalNames(classArray.get(), count);
187 schema->_objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:count];
188 for (Class cls in classes) {
189 if (!RLMIsObjectSubclass(cls)) {
190 @throw RLMException(@"Can't add non-Object type '%@' to a schema.", cls);
192 schema->_objectSchemaByName[[cls className]] = RLMRegisterClass(cls);
196 NSMutableArray *errors = [NSMutableArray new];
197 // Verify that all of the targets of links are included in the class list
198 [schema->_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(id, RLMObjectSchema *objectSchema, BOOL *) {
199 for (RLMProperty *prop in objectSchema.properties) {
200 if (prop.type != RLMPropertyTypeObject) {
203 if (!schema->_objectSchemaByName[prop.objectClassName]) {
204 [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]];
209 @throw RLMException(@"Invalid class subset list:\n%@", [errors componentsJoinedByString:@"\n"]);
215 + (RLMObjectSchema *)sharedSchemaForClass:(Class)cls {
216 @synchronized(s_localNameToClass) {
217 // We create instances of Swift objects during schema init, and they
218 // obviously need to not also try to initialize the schema
219 if (s_sharedSchemaState == SharedSchemaState::Initializing) {
223 RLMRegisterClassLocalNames(&cls, 1);
224 RLMObjectSchema *objectSchema = RLMRegisterClass(cls);
225 [cls initializeLinkedObjectSchemas];
230 + (instancetype)partialSharedSchema {
231 return s_sharedSchema;
234 + (instancetype)partialPrivateSharedSchema {
235 return s_privateSharedSchema;
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;
249 if (s_sharedSchemaState == SharedSchemaState::Initializing) {
250 @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));
253 s_sharedSchemaState = SharedSchemaState::Initializing;
255 // Make sure we've discovered all classes
257 unsigned int numClasses;
258 using malloc_ptr = std::unique_ptr<__unsafe_unretained Class[], decltype(&free)>;
259 malloc_ptr classes(objc_copyClassList(&numClasses), &free);
260 RLMRegisterClassLocalNames(classes.get(), numClasses);
263 [s_localNameToClass enumerateKeysAndObjectsUsingBlock:^(NSString *, Class cls, BOOL *) {
264 RLMRegisterClass(cls);
268 s_sharedSchemaState = SharedSchemaState::Uninitialized;
272 // Replace this method with one that doesn't need to acquire a lock
273 Class metaClass = objc_getMetaClass(class_getName(self));
274 IMP imp = imp_implementationWithBlock(^{ return s_sharedSchema; });
275 class_replaceMethod(metaClass, @selector(sharedSchema), imp, "@@:");
277 s_sharedSchemaState = SharedSchemaState::Initialized;
280 return s_sharedSchema;
283 // schema based on tables in a realm
284 + (instancetype)dynamicSchemaFromObjectStoreSchema:(Schema const&)objectStoreSchema {
285 // cache descriptors for all subclasses of RLMObject
286 NSMutableArray *schemaArray = [NSMutableArray arrayWithCapacity:objectStoreSchema.size()];
287 for (auto &objectSchema : objectStoreSchema) {
288 RLMObjectSchema *schema = [RLMObjectSchema objectSchemaForObjectStoreSchema:objectSchema];
289 [schemaArray addObject:schema];
292 // set class array and mapping
293 RLMSchema *schema = [RLMSchema new];
294 schema.objectSchema = schemaArray;
298 + (Class)classForString:(NSString *)className {
299 if (Class cls = s_localNameToClass[className]) {
303 if (Class cls = NSClassFromString(className)) {
304 return RLMIsObjectSubclass(cls) ? cls : nil;
307 // className might be the local name of a Swift class we haven't registered
308 // yet, so scan them all then recheck
310 unsigned int numClasses;
311 std::unique_ptr<__unsafe_unretained Class[], decltype(&free)> classes(objc_copyClassList(&numClasses), &free);
312 RLMRegisterClassLocalNames(classes.get(), numClasses);
315 return s_localNameToClass[className];
318 - (id)copyWithZone:(NSZone *)zone {
319 RLMSchema *schema = [[RLMSchema allocWithZone:zone] init];
320 schema->_objectSchemaByName = [[NSMutableDictionary allocWithZone:zone]
321 initWithDictionary:_objectSchemaByName copyItems:YES];
325 - (BOOL)isEqualToSchema:(RLMSchema *)schema {
326 if (_objectSchemaByName.count != schema->_objectSchemaByName.count) {
329 __block BOOL matches = YES;
330 [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RLMObjectSchema *objectSchema, BOOL *stop) {
331 if (![schema->_objectSchemaByName[name] isEqualToObjectSchema:objectSchema]) {
339 - (NSString *)description {
340 NSMutableString *objectSchemaString = [NSMutableString string];
341 NSArray *sort = @[[NSSortDescriptor sortDescriptorWithKey:@"className" ascending:YES]];
342 for (RLMObjectSchema *objectSchema in [self.objectSchema sortedArrayUsingDescriptors:sort]) {
343 [objectSchemaString appendFormat:@"\t%@\n",
344 [objectSchema.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
346 return [NSString stringWithFormat:@"Schema {\n%@}", objectSchemaString];
349 - (Schema)objectStoreCopy {
350 if (_objectStoreSchema.size() == 0) {
351 std::vector<realm::ObjectSchema> schema;
352 schema.reserve(_objectSchemaByName.count);
353 [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:[&](NSString *, RLMObjectSchema *objectSchema, BOOL *) {
354 schema.push_back(objectSchema.objectStoreCopy);
356 _objectStoreSchema = std::move(schema);
358 return _objectStoreSchema;