An amazing project that generates micro reports from tournament results
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

375 lines
14 KiB

////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMSchema_Private.h"
#import "RLMAccessor.h"
#import "RLMObjectBase_Private.h"
#import "RLMObject_Private.hpp"
#import "RLMObjectSchema_Private.hpp"
#import "RLMProperty_Private.h"
#import "RLMRealm_Private.hpp"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
#import "object_schema.hpp"
#import "object_store.hpp"
#import "schema.hpp"
#import <realm/group.hpp>
#import <objc/runtime.h>
#include <mutex>
using namespace realm;
const uint64_t RLMNotVersioned = realm::ObjectStore::NotVersioned;
// RLMSchema private properties
@interface RLMSchema ()
@property (nonatomic, readwrite) NSMutableDictionary *objectSchemaByName;
@end
// Private RLMSchema subclass that skips class registration on lookup
@interface RLMPrivateSchema : RLMSchema
@end
@implementation RLMPrivateSchema
- (RLMObjectSchema *)schemaForClassName:(NSString *)className {
return self.objectSchemaByName[className];
}
- (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className {
return [self schemaForClassName:className];
}
@end
static RLMSchema *s_sharedSchema = [[RLMSchema alloc] init];
static NSMutableDictionary *s_localNameToClass = [[NSMutableDictionary alloc] init];
static RLMSchema *s_privateSharedSchema = [[RLMPrivateSchema alloc] init];
static enum class SharedSchemaState {
Uninitialized,
Initializing,
Initialized
} s_sharedSchemaState = SharedSchemaState::Uninitialized;
@implementation RLMSchema {
NSArray *_objectSchema;
realm::Schema _objectStoreSchema;
}
// Caller must @synchronize on s_localNameToClass
static RLMObjectSchema *RLMRegisterClass(Class cls) {
if (RLMObjectSchema *schema = s_privateSharedSchema[[cls className]]) {
return schema;
}
auto prevState = s_sharedSchemaState;
s_sharedSchemaState = SharedSchemaState::Initializing;
RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:cls];
s_sharedSchemaState = prevState;
// set unmanaged class on shared shema for unmanaged object creation
schema.unmanagedClass = RLMUnmanagedAccessorClassForObjectClass(schema.objectClass, schema);
// override sharedSchema class methods for performance
RLMReplaceSharedSchemaMethod(cls, schema);
s_privateSharedSchema.objectSchemaByName[schema.className] = schema;
if ([cls shouldIncludeInDefaultSchema] && prevState != SharedSchemaState::Initialized) {
s_sharedSchema.objectSchemaByName[schema.className] = schema;
}
return schema;
}
// Caller must @synchronize on s_localNameToClass
static void RLMRegisterClassLocalNames(Class *classes, NSUInteger count) {
for (NSUInteger i = 0; i < count; i++) {
Class cls = classes[i];
if (!RLMIsObjectSubclass(cls)) {
continue;
}
NSString *className = NSStringFromClass(cls);
if ([className hasPrefix:@"RLM:"] || [className hasPrefix:@"NSKVONotifying"]) {
continue;
}
if ([RLMSwiftSupport isSwiftClassName:className]) {
className = [RLMSwiftSupport demangleClassName:className];
}
// NSStringFromClass demangles the names for top-level Swift classes
// but not for nested classes. _T indicates it's a Swift symbol, t
// indicates it's a type, and C indicates it's a class.
else if ([className hasPrefix:@"_TtC"]) {
@throw RLMException(@"RLMObject subclasses cannot be nested within other declarations. Please move %@ to global scope.", className);
}
if (Class existingClass = s_localNameToClass[className]) {
if (existingClass != cls) {
@throw RLMException(@"RLMObject subclasses with the same name cannot be included twice in the same target. "
@"Please make sure '%@' is only linked once to your current target.", className);
}
continue;
}
s_localNameToClass[className] = cls;
RLMReplaceClassNameMethod(cls, className);
}
}
- (instancetype)init {
self = [super init];
if (self) {
_objectSchemaByName = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSArray *)objectSchema {
if (!_objectSchema) {
_objectSchema = [_objectSchemaByName allValues];
}
return _objectSchema;
}
- (void)setObjectSchema:(NSArray *)objectSchema {
_objectSchema = objectSchema;
_objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:objectSchema.count];
for (RLMObjectSchema *object in objectSchema) {
[_objectSchemaByName setObject:object forKey:object.className];
}
}
- (RLMObjectSchema *)schemaForClassName:(NSString *)className {
if (RLMObjectSchema *schema = _objectSchemaByName[className]) {
return schema; // fast path for already-initialized schemas
} else if (Class cls = [RLMSchema classForString:className]) {
[cls sharedSchema]; // initialize the schema
return _objectSchemaByName[className]; // try again
} else {
return nil;
}
}
- (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)className {
RLMObjectSchema *schema = [self schemaForClassName:className];
if (!schema) {
@throw RLMException(@"Object type '%@' not managed by the Realm", className);
}
return schema;
}
+ (instancetype)schemaWithObjectClasses:(NSArray *)classes {
NSUInteger count = classes.count;
auto classArray = std::make_unique<__unsafe_unretained Class[]>(count);
[classes getObjects:classArray.get() range:NSMakeRange(0, count)];
RLMSchema *schema = [[self alloc] init];
@synchronized(s_localNameToClass) {
RLMRegisterClassLocalNames(classArray.get(), count);
schema->_objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:count];
for (Class cls in classes) {
if (!RLMIsObjectSubclass(cls)) {
@throw RLMException(@"Can't add non-Object type '%@' to a schema.", cls);
}
schema->_objectSchemaByName[[cls className]] = RLMRegisterClass(cls);
}
}
NSMutableArray *errors = [NSMutableArray new];
// Verify that all of the targets of links are included in the class list
[schema->_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(id, RLMObjectSchema *objectSchema, BOOL *) {
for (RLMProperty *prop in objectSchema.properties) {
if (prop.type != RLMPropertyTypeObject) {
continue;
}
if (!schema->_objectSchemaByName[prop.objectClassName]) {
[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]];
}
}
}];
if (errors.count) {
@throw RLMException(@"Invalid class subset list:\n%@", [errors componentsJoinedByString:@"\n"]);
}
return schema;
}
+ (RLMObjectSchema *)sharedSchemaForClass:(Class)cls {
@synchronized(s_localNameToClass) {
// We create instances of Swift objects during schema init, and they
// obviously need to not also try to initialize the schema
if (s_sharedSchemaState == SharedSchemaState::Initializing) {
return nil;
}
RLMRegisterClassLocalNames(&cls, 1);
RLMObjectSchema *objectSchema = RLMRegisterClass(cls);
[cls initializeLinkedObjectSchemas];
return objectSchema;
}
}
+ (instancetype)partialSharedSchema {
return s_sharedSchema;
}
+ (instancetype)partialPrivateSharedSchema {
return s_privateSharedSchema;
}
// schema based on runtime objects
+ (instancetype)sharedSchema {
@synchronized(s_localNameToClass) {
// We replace this method with one which just returns s_sharedSchema
// once initialization is complete, but we still need to check if it's
// already complete because it may have been done by another thread
// while we were waiting for the lock
if (s_sharedSchemaState == SharedSchemaState::Initialized) {
return s_sharedSchema;
}
if (s_sharedSchemaState == SharedSchemaState::Initializing) {
@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));
}
s_sharedSchemaState = SharedSchemaState::Initializing;
try {
// Make sure we've discovered all classes
{
unsigned int numClasses;
using malloc_ptr = std::unique_ptr<__unsafe_unretained Class[], decltype(&free)>;
malloc_ptr classes(objc_copyClassList(&numClasses), &free);
RLMRegisterClassLocalNames(classes.get(), numClasses);
}
[s_localNameToClass enumerateKeysAndObjectsUsingBlock:^(NSString *, Class cls, BOOL *) {
RLMRegisterClass(cls);
}];
}
catch (...) {
s_sharedSchemaState = SharedSchemaState::Uninitialized;
throw;
}
// Replace this method with one that doesn't need to acquire a lock
Class metaClass = objc_getMetaClass(class_getName(self));
IMP imp = imp_implementationWithBlock(^{ return s_sharedSchema; });
class_replaceMethod(metaClass, @selector(sharedSchema), imp, "@@:");
s_sharedSchemaState = SharedSchemaState::Initialized;
}
return s_sharedSchema;
}
// schema based on tables in a realm
+ (instancetype)dynamicSchemaFromObjectStoreSchema:(Schema const&)objectStoreSchema {
// cache descriptors for all subclasses of RLMObject
NSMutableArray *schemaArray = [NSMutableArray arrayWithCapacity:objectStoreSchema.size()];
for (auto &objectSchema : objectStoreSchema) {
RLMObjectSchema *schema = [RLMObjectSchema objectSchemaForObjectStoreSchema:objectSchema];
[schemaArray addObject:schema];
}
// set class array and mapping
RLMSchema *schema = [RLMSchema new];
schema.objectSchema = schemaArray;
return schema;
}
+ (Class)classForString:(NSString *)className {
if (Class cls = s_localNameToClass[className]) {
return cls;
}
if (Class cls = NSClassFromString(className)) {
return RLMIsObjectSubclass(cls) ? cls : nil;
}
// className might be the local name of a Swift class we haven't registered
// yet, so scan them all then recheck
{
unsigned int numClasses;
std::unique_ptr<__unsafe_unretained Class[], decltype(&free)> classes(objc_copyClassList(&numClasses), &free);
RLMRegisterClassLocalNames(classes.get(), numClasses);
}
return s_localNameToClass[className];
}
- (id)copyWithZone:(NSZone *)zone {
RLMSchema *schema = [[RLMSchema allocWithZone:zone] init];
schema->_objectSchemaByName = [[NSMutableDictionary allocWithZone:zone]
initWithDictionary:_objectSchemaByName copyItems:YES];
return schema;
}
- (BOOL)isEqualToSchema:(RLMSchema *)schema {
if (_objectSchemaByName.count != schema->_objectSchemaByName.count) {
return NO;
}
__block BOOL matches = YES;
[_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RLMObjectSchema *objectSchema, BOOL *stop) {
if (![schema->_objectSchemaByName[name] isEqualToObjectSchema:objectSchema]) {
*stop = YES;
matches = NO;
}
}];
return matches;
}
- (NSString *)description {
NSMutableString *objectSchemaString = [NSMutableString string];
NSArray *sort = @[[NSSortDescriptor sortDescriptorWithKey:@"className" ascending:YES]];
for (RLMObjectSchema *objectSchema in [self.objectSchema sortedArrayUsingDescriptors:sort]) {
[objectSchemaString appendFormat:@"\t%@\n",
[objectSchema.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
}
return [NSString stringWithFormat:@"Schema {\n%@}", objectSchemaString];
}
- (Schema)objectStoreCopy {
if (_objectStoreSchema.size() == 0) {
std::vector<realm::ObjectSchema> schema;
schema.reserve(_objectSchemaByName.count);
[_objectSchemaByName enumerateKeysAndObjectsUsingBlock:[&](NSString *, RLMObjectSchema *objectSchema, BOOL *) {
schema.push_back([objectSchema objectStoreCopy:self]);
}];
// Having both obj-c and Swift classes for the same tables results in
// duplicate ObjectSchemas that we need to filter out
std::sort(begin(schema), end(schema), [](auto&& a, auto&& b) { return a.name < b.name; });
schema.erase(std::unique(begin(schema), end(schema), [](auto&& a, auto&& b) {
if (a.name == b.name) {
// If we make _realmObjectName public this needs to be turned into an exception
REALM_ASSERT_DEBUG(a.persisted_properties == b.persisted_properties);
return true;
}
return false;
}), end(schema));
_objectStoreSchema = std::move(schema);
}
return _objectStoreSchema;
}
@end