added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMSchema.mm
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
19 #import "RLMSchema_Private.h"
20
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"
28 #import "RLMUtil.hpp"
29
30 #import "object_store.hpp"
31 #import "schema.hpp"
32
33 #import <realm/group.hpp>
34
35 #import <objc/runtime.h>
36 #include <mutex>
37
38 using namespace realm;
39
40 const uint64_t RLMNotVersioned = realm::ObjectStore::NotVersioned;
41
42 // RLMSchema private properties
43 @interface RLMSchema ()
44 @property (nonatomic, readwrite) NSMutableDictionary *objectSchemaByName;
45 @end
46
47 // Private RLMSchema subclass that skips class registration on lookup
48 @interface RLMPrivateSchema : RLMSchema
49 @end
50 @implementation RLMPrivateSchema
51 - (RLMObjectSchema *)schemaForClassName:(NSString *)className {
52     return self.objectSchemaByName[className];
53 }
54
55 - (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className {
56     return [self schemaForClassName:className];
57 }
58 @end
59
60 static RLMSchema *s_sharedSchema = [[RLMSchema alloc] init];
61 static NSMutableDictionary *s_localNameToClass = [[NSMutableDictionary alloc] init];
62 static RLMSchema *s_privateSharedSchema = [[RLMPrivateSchema alloc] init];
63
64 static enum class SharedSchemaState {
65     Uninitialized,
66     Initializing,
67     Initialized
68 } s_sharedSchemaState = SharedSchemaState::Uninitialized;
69
70 @implementation RLMSchema {
71     NSArray *_objectSchema;
72     realm::Schema _objectStoreSchema;
73 }
74
75 // Caller must @synchronize on s_localNameToClass
76 static RLMObjectSchema *RLMRegisterClass(Class cls) {
77     if (RLMObjectSchema *schema = s_privateSharedSchema[[cls className]]) {
78         return schema;
79     }
80
81     auto prevState = s_sharedSchemaState;
82     s_sharedSchemaState = SharedSchemaState::Initializing;
83     RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:cls];
84     s_sharedSchemaState = prevState;
85
86     // set unmanaged class on shared shema for unmanaged object creation
87     schema.unmanagedClass = RLMUnmanagedAccessorClassForObjectClass(schema.objectClass, schema);
88
89     // override sharedSchema class methods for performance
90     RLMReplaceSharedSchemaMethod(cls, schema);
91
92     s_privateSharedSchema.objectSchemaByName[schema.className] = schema;
93     if ([cls shouldIncludeInDefaultSchema] && prevState != SharedSchemaState::Initialized) {
94         s_sharedSchema.objectSchemaByName[schema.className] = schema;
95     }
96
97     return schema;
98 }
99
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)) {
105             continue;
106         }
107
108         NSString *className = NSStringFromClass(cls);
109         if ([className hasPrefix:@"RLM:"] || [className hasPrefix:@"NSKVONotifying"]) {
110             continue;
111         }
112
113         if ([RLMSwiftSupport isSwiftClassName:className]) {
114             className = [RLMSwiftSupport demangleClassName:className];
115         }
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);
121         }
122
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);
127             }
128             continue;
129         }
130
131         s_localNameToClass[className] = cls;
132         RLMReplaceClassNameMethod(cls, className);
133     }
134 }
135
136 - (instancetype)init {
137     self = [super init];
138     if (self) {
139         _objectSchemaByName = [[NSMutableDictionary alloc] init];
140     }
141     return self;
142 }
143
144 - (NSArray *)objectSchema {
145     if (!_objectSchema) {
146         _objectSchema = [_objectSchemaByName allValues];
147     }
148     return _objectSchema;
149 }
150
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];
156     }
157 }
158
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
165     } else {
166         return nil;
167     }
168 }
169
170 - (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className {
171     RLMObjectSchema *schema = [self schemaForClassName:className];
172     if (!schema) {
173         @throw RLMException(@"Object type '%@' not managed by the Realm", className);
174     }
175     return schema;
176 }
177
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)];
182
183     RLMSchema *schema = [[self alloc] init];
184     @synchronized(s_localNameToClass) {
185         RLMRegisterClassLocalNames(classArray.get(), count);
186
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);
191             }
192             schema->_objectSchemaByName[[cls className]] = RLMRegisterClass(cls);
193         }
194     }
195
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) {
201                 continue;
202             }
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]];
205             }
206         }
207     }];
208     if (errors.count) {
209         @throw RLMException(@"Invalid class subset list:\n%@", [errors componentsJoinedByString:@"\n"]);
210     }
211
212     return schema;
213 }
214
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) {
220             return nil;
221         }
222
223         RLMRegisterClassLocalNames(&cls, 1);
224         RLMObjectSchema *objectSchema = RLMRegisterClass(cls);
225         [cls initializeLinkedObjectSchemas];
226         return objectSchema;
227     }
228 }
229
230 + (instancetype)partialSharedSchema {
231     return s_sharedSchema;
232 }
233
234 + (instancetype)partialPrivateSharedSchema {
235     return s_privateSharedSchema;
236 }
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
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));
251         }
252
253         s_sharedSchemaState = SharedSchemaState::Initializing;
254         try {
255             // Make sure we've discovered all classes
256             {
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);
261             }
262
263             [s_localNameToClass enumerateKeysAndObjectsUsingBlock:^(NSString *, Class cls, BOOL *) {
264                 RLMRegisterClass(cls);
265             }];
266         }
267         catch (...) {
268             s_sharedSchemaState = SharedSchemaState::Uninitialized;
269             throw;
270         }
271
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, "@@:");
276
277         s_sharedSchemaState = SharedSchemaState::Initialized;
278     }
279
280     return s_sharedSchema;
281 }
282
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];
290     }
291
292     // set class array and mapping
293     RLMSchema *schema = [RLMSchema new];
294     schema.objectSchema = schemaArray;
295     return schema;
296 }
297
298 + (Class)classForString:(NSString *)className {
299     if (Class cls = s_localNameToClass[className]) {
300         return cls;
301     }
302
303     if (Class cls = NSClassFromString(className)) {
304         return RLMIsObjectSubclass(cls) ? cls : nil;
305     }
306
307     // className might be the local name of a Swift class we haven't registered
308     // yet, so scan them all then recheck
309     {
310         unsigned int numClasses;
311         std::unique_ptr<__unsafe_unretained Class[], decltype(&free)> classes(objc_copyClassList(&numClasses), &free);
312         RLMRegisterClassLocalNames(classes.get(), numClasses);
313     }
314
315     return s_localNameToClass[className];
316 }
317
318 - (id)copyWithZone:(NSZone *)zone {
319     RLMSchema *schema = [[RLMSchema allocWithZone:zone] init];
320     schema->_objectSchemaByName = [[NSMutableDictionary allocWithZone:zone]
321                                    initWithDictionary:_objectSchemaByName copyItems:YES];
322     return schema;
323 }
324
325 - (BOOL)isEqualToSchema:(RLMSchema *)schema {
326     if (_objectSchemaByName.count != schema->_objectSchemaByName.count) {
327         return NO;
328     }
329     __block BOOL matches = YES;
330     [_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RLMObjectSchema *objectSchema, BOOL *stop) {
331         if (![schema->_objectSchemaByName[name] isEqualToObjectSchema:objectSchema]) {
332             *stop = YES;
333             matches = NO;
334         }
335     }];
336     return matches;
337 }
338
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"]];
345     }
346     return [NSString stringWithFormat:@"Schema {\n%@}", objectSchemaString];
347 }
348
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);
355         }];
356         _objectStoreSchema = std::move(schema);
357     }
358     return _objectStoreSchema;
359 }
360
361 @end