543 lines
17 KiB
Plaintext
543 lines
17 KiB
Plaintext
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 "RLMObject_Private.hpp"
|
|
|
|
#import "RLMAccessor.h"
|
|
#import "RLMArray_Private.hpp"
|
|
#import "RLMListBase.h"
|
|
#import "RLMObjectSchema_Private.hpp"
|
|
#import "RLMObjectStore.h"
|
|
#import "RLMObservation.hpp"
|
|
#import "RLMOptionalBase.h"
|
|
#import "RLMProperty_Private.h"
|
|
#import "RLMRealm_Private.hpp"
|
|
#import "RLMSchema_Private.h"
|
|
#import "RLMSwiftSupport.h"
|
|
#import "RLMThreadSafeReference_Private.hpp"
|
|
#import "RLMUtil.hpp"
|
|
|
|
#import "object.hpp"
|
|
#import "object_schema.hpp"
|
|
#import "shared_realm.hpp"
|
|
|
|
using namespace realm;
|
|
|
|
const NSUInteger RLMDescriptionMaxDepth = 5;
|
|
|
|
static bool maybeInitObjectSchemaForUnmanaged(RLMObjectBase *obj) {
|
|
obj->_objectSchema = [obj.class sharedSchema];
|
|
if (!obj->_objectSchema) {
|
|
return false;
|
|
}
|
|
|
|
// set default values
|
|
if (!obj->_objectSchema.isSwiftClass) {
|
|
NSDictionary *dict = RLMDefaultValuesForObjectSchema(obj->_objectSchema);
|
|
for (NSString *key in dict) {
|
|
[obj setValue:dict[key] forKey:key];
|
|
}
|
|
}
|
|
|
|
// set unmanaged accessor class
|
|
object_setClass(obj, obj->_objectSchema.unmanagedClass);
|
|
return true;
|
|
}
|
|
|
|
@interface RLMObjectBase () <RLMThreadConfined, RLMThreadConfined_Private>
|
|
@end
|
|
|
|
@implementation RLMObjectBase
|
|
// unmanaged init
|
|
- (instancetype)init {
|
|
if ((self = [super init])) {
|
|
maybeInitObjectSchemaForUnmanaged(self);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
// This can't be a unique_ptr because associated objects are removed
|
|
// *after* c++ members are destroyed and dealloc is called, and we need it
|
|
// to be in a validish state when that happens
|
|
delete _observationInfo;
|
|
_observationInfo = nullptr;
|
|
}
|
|
|
|
static id coerceToObjectType(id obj, Class cls, RLMSchema *schema) {
|
|
return [obj isKindOfClass:cls] ? obj : [[cls alloc] initWithValue:obj schema:schema];
|
|
}
|
|
|
|
static id validatedObjectForProperty(__unsafe_unretained id const obj,
|
|
__unsafe_unretained RLMObjectSchema *const objectSchema,
|
|
__unsafe_unretained RLMProperty *const prop,
|
|
__unsafe_unretained RLMSchema *const schema) {
|
|
RLMValidateValueForProperty(obj, objectSchema, prop);
|
|
if (!obj || obj == NSNull.null) {
|
|
return nil;
|
|
}
|
|
if (prop.type == RLMPropertyTypeObject) {
|
|
Class objectClass = schema[prop.objectClassName].objectClass;
|
|
if (prop.array) {
|
|
NSMutableArray *ret = [[NSMutableArray alloc] init];
|
|
for (id el in obj) {
|
|
[ret addObject:coerceToObjectType(el, objectClass, schema)];
|
|
}
|
|
return ret;
|
|
}
|
|
return coerceToObjectType(obj, objectClass, schema);
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
- (instancetype)initWithValue:(id)value schema:(RLMSchema *)schema {
|
|
if (!(self = [super init])) {
|
|
return self;
|
|
}
|
|
|
|
if (!value || value == NSNull.null) {
|
|
@throw RLMException(@"Must provide a non-nil value.");
|
|
}
|
|
|
|
if (!maybeInitObjectSchemaForUnmanaged(self)) {
|
|
// Don't populate fields from the passed-in object if we're called
|
|
// during schema init
|
|
return self;
|
|
}
|
|
|
|
NSArray *properties = _objectSchema.properties;
|
|
if (NSArray *array = RLMDynamicCast<NSArray>(value)) {
|
|
if (array.count > properties.count) {
|
|
@throw RLMException(@"Invalid array input: more values (%llu) than properties (%llu).",
|
|
(unsigned long long)array.count, (unsigned long long)properties.count);
|
|
}
|
|
NSUInteger i = 0;
|
|
for (id val in array) {
|
|
RLMProperty *prop = properties[i++];
|
|
[self setValue:validatedObjectForProperty(RLMCoerceToNil(val), _objectSchema, prop, schema)
|
|
forKey:prop.name];
|
|
}
|
|
}
|
|
else {
|
|
// assume our object is an NSDictionary or an object with kvc properties
|
|
for (RLMProperty *prop in properties) {
|
|
id obj = RLMValidatedValueForProperty(value, prop.name, _objectSchema.className);
|
|
|
|
// don't set unspecified properties
|
|
if (!obj) {
|
|
continue;
|
|
}
|
|
|
|
[self setValue:validatedObjectForProperty(RLMCoerceToNil(obj), _objectSchema, prop, schema)
|
|
forKey:prop.name];
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
id RLMCreateManagedAccessor(Class cls, __unsafe_unretained RLMRealm *realm, RLMClassInfo *info) {
|
|
RLMObjectBase *obj = [[cls alloc] initWithRealm:realm schema:info->rlmObjectSchema];
|
|
obj->_info = info;
|
|
return obj;
|
|
}
|
|
|
|
- (instancetype)initWithRealm:(__unsafe_unretained RLMRealm *const)realm
|
|
schema:(RLMObjectSchema *)schema {
|
|
self = [super init];
|
|
if (self) {
|
|
_realm = realm;
|
|
_objectSchema = schema;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)valueForKey:(NSString *)key {
|
|
if (_observationInfo) {
|
|
return _observationInfo->valueForKey(key);
|
|
}
|
|
return [super valueForKey:key];
|
|
}
|
|
|
|
// Generic Swift properties can't be dynamic, so KVO doesn't work for them by default
|
|
- (id)valueForUndefinedKey:(NSString *)key {
|
|
if (Ivar ivar = _objectSchema[key].swiftIvar) {
|
|
return RLMCoerceToNil(object_getIvar(self, ivar));
|
|
}
|
|
return [super valueForUndefinedKey:key];
|
|
}
|
|
|
|
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
|
|
value = RLMCoerceToNil(value);
|
|
RLMProperty *property = _objectSchema[key];
|
|
if (Ivar ivar = property.swiftIvar) {
|
|
if (property.array && (!value || [value conformsToProtocol:@protocol(NSFastEnumeration)])) {
|
|
RLMArray *array = [object_getIvar(self, ivar) _rlmArray];
|
|
[array removeAllObjects];
|
|
|
|
if (value) {
|
|
[array addObjects:validatedObjectForProperty(value, _objectSchema, property,
|
|
RLMSchema.partialPrivateSharedSchema)];
|
|
}
|
|
}
|
|
else if (property.optional) {
|
|
RLMSetOptional(object_getIvar(self, ivar), value);
|
|
}
|
|
return;
|
|
}
|
|
[super setValue:value forUndefinedKey:key];
|
|
}
|
|
|
|
// overridden at runtime per-class for performance
|
|
+ (NSString *)className {
|
|
NSString *className = NSStringFromClass(self);
|
|
if ([RLMSwiftSupport isSwiftClassName:className]) {
|
|
className = [RLMSwiftSupport demangleClassName:className];
|
|
}
|
|
return className;
|
|
}
|
|
|
|
// overridden at runtime per-class for performance
|
|
+ (RLMObjectSchema *)sharedSchema {
|
|
return [RLMSchema sharedSchemaForClass:self.class];
|
|
}
|
|
|
|
+ (void)initializeLinkedObjectSchemas {
|
|
for (RLMProperty *prop in self.sharedSchema.properties) {
|
|
if (prop.type == RLMPropertyTypeObject && !RLMSchema.partialPrivateSharedSchema[prop.objectClassName]) {
|
|
[[RLMSchema classForString:prop.objectClassName] initializeLinkedObjectSchemas];
|
|
}
|
|
}
|
|
}
|
|
|
|
+ (Class)objectUtilClass:(BOOL)isSwift {
|
|
return RLMObjectUtilClass(isSwift);
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
if (self.isInvalidated) {
|
|
return @"[invalid object]";
|
|
}
|
|
|
|
return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
|
|
}
|
|
|
|
- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
|
|
if (depth == 0) {
|
|
return @"<Maximum depth exceeded>";
|
|
}
|
|
|
|
NSString *baseClassName = _objectSchema.className;
|
|
NSMutableString *mString = [NSMutableString stringWithFormat:@"%@ {\n", baseClassName];
|
|
|
|
for (RLMProperty *property in _objectSchema.properties) {
|
|
id object = [(id)self objectForKeyedSubscript:property.name];
|
|
NSString *sub;
|
|
if ([object respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
|
|
sub = [object descriptionWithMaxDepth:depth - 1];
|
|
}
|
|
else if (property.type == RLMPropertyTypeData) {
|
|
static NSUInteger maxPrintedDataLength = 24;
|
|
NSData *data = object;
|
|
NSUInteger length = data.length;
|
|
if (length > maxPrintedDataLength) {
|
|
data = [NSData dataWithBytes:data.bytes length:maxPrintedDataLength];
|
|
}
|
|
NSString *dataDescription = [data description];
|
|
sub = [NSString stringWithFormat:@"<%@ — %lu total bytes>", [dataDescription substringWithRange:NSMakeRange(1, dataDescription.length - 2)], (unsigned long)length];
|
|
}
|
|
else {
|
|
sub = [object description];
|
|
}
|
|
[mString appendFormat:@"\t%@ = %@;\n", property.name, [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
|
|
}
|
|
[mString appendString:@"}"];
|
|
|
|
return [NSString stringWithString:mString];
|
|
}
|
|
|
|
- (RLMRealm *)realm {
|
|
return _realm;
|
|
}
|
|
|
|
- (RLMObjectSchema *)objectSchema {
|
|
return _objectSchema;
|
|
}
|
|
|
|
- (BOOL)isInvalidated {
|
|
// if not unmanaged and our accessor has been detached, we have been deleted
|
|
return self.class == _objectSchema.accessorClass && !_row.is_attached();
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)object {
|
|
if (RLMObjectBase *other = RLMDynamicCast<RLMObjectBase>(object)) {
|
|
if (_objectSchema.primaryKeyProperty) {
|
|
return RLMObjectBaseAreEqual(self, other);
|
|
}
|
|
}
|
|
return [super isEqual:object];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
if (_objectSchema.primaryKeyProperty) {
|
|
id primaryProperty = [self valueForKey:_objectSchema.primaryKeyProperty.name];
|
|
|
|
// modify the hash of our primary key value to avoid potential (although unlikely) collisions
|
|
return [primaryProperty hash] ^ 1;
|
|
}
|
|
else {
|
|
return [super hash];
|
|
}
|
|
}
|
|
|
|
+ (BOOL)shouldIncludeInDefaultSchema {
|
|
return RLMIsObjectSubclass(self);
|
|
}
|
|
|
|
+ (NSString *)_realmObjectName {
|
|
return nil;
|
|
}
|
|
|
|
+ (NSDictionary *)_realmColumnNames {
|
|
return nil;
|
|
}
|
|
|
|
- (id)mutableArrayValueForKey:(NSString *)key {
|
|
id obj = [self valueForKey:key];
|
|
if ([obj isKindOfClass:[RLMArray class]]) {
|
|
return obj;
|
|
}
|
|
return [super mutableArrayValueForKey:key];
|
|
}
|
|
|
|
- (void)addObserver:(id)observer
|
|
forKeyPath:(NSString *)keyPath
|
|
options:(NSKeyValueObservingOptions)options
|
|
context:(void *)context {
|
|
if (!_observationInfo) {
|
|
_observationInfo = new RLMObservationInfo(self);
|
|
}
|
|
_observationInfo->recordObserver(_row, _info, _objectSchema, keyPath);
|
|
|
|
[super addObserver:observer forKeyPath:keyPath options:options context:context];
|
|
}
|
|
|
|
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
|
|
[super removeObserver:observer forKeyPath:keyPath];
|
|
if (_observationInfo)
|
|
_observationInfo->removeObserver();
|
|
}
|
|
|
|
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
|
|
const char *className = class_getName(self);
|
|
const char accessorClassPrefix[] = "RLM:Managed";
|
|
if (!strncmp(className, accessorClassPrefix, sizeof(accessorClassPrefix) - 1)) {
|
|
if ([class_getSuperclass(self.class) sharedSchema][key]) {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
return [super automaticallyNotifiesObserversForKey:key];
|
|
}
|
|
|
|
#pragma mark - Thread Confined Protocol Conformance
|
|
|
|
- (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
|
|
Object object(_realm->_realm, *_info->objectSchema, _row);
|
|
realm::ThreadSafeReference<Object> reference = _realm->_realm->obtain_thread_safe_reference(std::move(object));
|
|
return std::make_unique<realm::ThreadSafeReference<Object>>(std::move(reference));
|
|
}
|
|
|
|
- (id)objectiveCMetadata {
|
|
return nil;
|
|
}
|
|
|
|
+ (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
|
|
metadata:(__unused id)metadata
|
|
realm:(RLMRealm *)realm {
|
|
REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<Object> *>(reference.get()));
|
|
auto object_reference = static_cast<realm::ThreadSafeReference<Object> *>(reference.get());
|
|
|
|
Object object = realm->_realm->resolve_thread_safe_reference(std::move(*object_reference));
|
|
if (!object.is_valid()) {
|
|
return nil;
|
|
}
|
|
NSString *objectClassName = @(object.get_object_schema().name.c_str());
|
|
|
|
return RLMCreateObjectAccessor(realm, realm->_info[objectClassName], object.row().get_index());
|
|
}
|
|
|
|
@end
|
|
|
|
RLMRealm *RLMObjectBaseRealm(__unsafe_unretained RLMObjectBase *object) {
|
|
return object ? object->_realm : nil;
|
|
}
|
|
|
|
RLMObjectSchema *RLMObjectBaseObjectSchema(__unsafe_unretained RLMObjectBase *object) {
|
|
return object ? object->_objectSchema : nil;
|
|
}
|
|
|
|
id RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase *object, NSString *key) {
|
|
if (!object) {
|
|
return nil;
|
|
}
|
|
|
|
if (object->_realm) {
|
|
return RLMDynamicGetByName(object, key, false);
|
|
}
|
|
else {
|
|
return [object valueForKey:key];
|
|
}
|
|
}
|
|
|
|
void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase *object, NSString *key, id obj) {
|
|
if (!object) {
|
|
return;
|
|
}
|
|
|
|
if (object->_realm || object.class == object->_objectSchema.accessorClass) {
|
|
RLMDynamicValidatedSet(object, key, obj);
|
|
}
|
|
else {
|
|
[object setValue:obj forKey:key];
|
|
}
|
|
}
|
|
|
|
|
|
BOOL RLMObjectBaseAreEqual(RLMObjectBase *o1, RLMObjectBase *o2) {
|
|
// if not the correct types throw
|
|
if ((o1 && ![o1 isKindOfClass:RLMObjectBase.class]) || (o2 && ![o2 isKindOfClass:RLMObjectBase.class])) {
|
|
@throw RLMException(@"Can only compare objects of class RLMObjectBase");
|
|
}
|
|
// if identical object (or both are nil)
|
|
if (o1 == o2) {
|
|
return YES;
|
|
}
|
|
// if one is nil
|
|
if (o1 == nil || o2 == nil) {
|
|
return NO;
|
|
}
|
|
// if not in realm or differing realms
|
|
if (o1->_realm == nil || o1->_realm != o2->_realm) {
|
|
return NO;
|
|
}
|
|
// if either are detached
|
|
if (!o1->_row.is_attached() || !o2->_row.is_attached()) {
|
|
return NO;
|
|
}
|
|
// if table and index are the same
|
|
return o1->_row.get_table() == o2->_row.get_table()
|
|
&& o1->_row.get_index() == o2->_row.get_index();
|
|
}
|
|
|
|
id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) {
|
|
@try {
|
|
return [object valueForKey:key];
|
|
}
|
|
@catch (NSException *e) {
|
|
if ([e.name isEqualToString:NSUndefinedKeyException]) {
|
|
@throw RLMException(@"Invalid value '%@' to initialize object of type '%@': missing key '%@'",
|
|
object, className, key);
|
|
}
|
|
@throw;
|
|
}
|
|
}
|
|
|
|
Class RLMObjectUtilClass(BOOL isSwift) {
|
|
static Class objectUtilObjc = [RLMObjectUtil class];
|
|
static Class objectUtilSwift = NSClassFromString(@"RealmSwiftObjectUtil");
|
|
return isSwift && objectUtilSwift ? objectUtilSwift : objectUtilObjc;
|
|
}
|
|
|
|
@implementation RLMObjectUtil
|
|
|
|
+ (NSArray *)ignoredPropertiesForClass:(Class)cls {
|
|
return [cls ignoredProperties];
|
|
}
|
|
|
|
+ (NSArray *)indexedPropertiesForClass:(Class)cls {
|
|
return [cls indexedProperties];
|
|
}
|
|
|
|
+ (NSDictionary *)linkingObjectsPropertiesForClass:(Class)cls {
|
|
return [cls linkingObjectsProperties];
|
|
}
|
|
|
|
+ (NSDictionary *)linkingObjectProperties:(__unused id)object {
|
|
return nil;
|
|
}
|
|
|
|
+ (NSArray *)getSwiftProperties:(__unused id)obj {
|
|
return nil;
|
|
}
|
|
|
|
+ (NSDictionary *)getOptionalProperties:(__unused id)obj {
|
|
return nil;
|
|
}
|
|
|
|
+ (NSArray *)requiredPropertiesForClass:(Class)cls {
|
|
return [cls requiredProperties];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RLMSwiftPropertyMetadata
|
|
|
|
+ (instancetype)metadataForOtherProperty:(NSString *)propertyName {
|
|
RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
|
|
md.propertyName = propertyName;
|
|
md.kind = RLMSwiftPropertyKindOther;
|
|
return md;
|
|
}
|
|
|
|
+ (instancetype)metadataForListProperty:(NSString *)propertyName {
|
|
RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
|
|
md.propertyName = propertyName;
|
|
md.kind = RLMSwiftPropertyKindList;
|
|
return md;
|
|
}
|
|
|
|
+ (instancetype)metadataForLinkingObjectsProperty:(NSString *)propertyName
|
|
className:(NSString *)className
|
|
linkedPropertyName:(NSString *)linkedPropertyName {
|
|
RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
|
|
md.propertyName = propertyName;
|
|
md.className = className;
|
|
md.linkedPropertyName = linkedPropertyName;
|
|
md.kind = RLMSwiftPropertyKindLinkingObjects;
|
|
return md;
|
|
}
|
|
|
|
+ (instancetype)metadataForOptionalProperty:(NSString *)propertyName type:(RLMPropertyType)type {
|
|
RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
|
|
md.propertyName = propertyName;
|
|
md.propertyType = type;
|
|
md.kind = RLMSwiftPropertyKindOptional;
|
|
return md;
|
|
}
|
|
|
|
+ (instancetype)metadataForNilLiteralOptionalProperty:(NSString *)propertyName {
|
|
RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
|
|
md.propertyName = propertyName;
|
|
md.kind = RLMSwiftPropertyKindNilLiteralOptional;
|
|
return md;
|
|
}
|
|
|
|
@end
|