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 "RLMObject_Private.hpp"
21 #import "RLMAccessor.h"
22 #import "RLMArray_Private.hpp"
23 #import "RLMListBase.h"
24 #import "RLMObjectSchema_Private.hpp"
25 #import "RLMObjectStore.h"
26 #import "RLMObservation.hpp"
27 #import "RLMOptionalBase.h"
28 #import "RLMProperty_Private.h"
29 #import "RLMRealm_Private.hpp"
30 #import "RLMSchema_Private.h"
31 #import "RLMSwiftSupport.h"
32 #import "RLMThreadSafeReference_Private.hpp"
36 #import "shared_realm.hpp"
38 using namespace realm;
40 const NSUInteger RLMDescriptionMaxDepth = 5;
42 static bool maybeInitObjectSchemaForUnmanaged(RLMObjectBase *obj) {
43 obj->_objectSchema = [obj.class sharedSchema];
44 if (!obj->_objectSchema) {
49 if (!obj->_objectSchema.isSwiftClass) {
50 NSDictionary *dict = RLMDefaultValuesForObjectSchema(obj->_objectSchema);
51 for (NSString *key in dict) {
52 [obj setValue:dict[key] forKey:key];
56 // set unmanaged accessor class
57 object_setClass(obj, obj->_objectSchema.unmanagedClass);
61 @interface RLMObjectBase () <RLMThreadConfined, RLMThreadConfined_Private>
64 @implementation RLMObjectBase
66 - (instancetype)init {
67 if ((self = [super init])) {
68 maybeInitObjectSchemaForUnmanaged(self);
74 // This can't be a unique_ptr because associated objects are removed
75 // *after* c++ members are destroyed and dealloc is called, and we need it
76 // to be in a validish state when that happens
77 delete _observationInfo;
78 _observationInfo = nullptr;
81 static id coerceToObjectType(id obj, Class cls, RLMSchema *schema) {
82 return [obj isKindOfClass:cls] ? obj : [[cls alloc] initWithValue:obj schema:schema];
85 static id validatedObjectForProperty(__unsafe_unretained id const obj,
86 __unsafe_unretained RLMObjectSchema *const objectSchema,
87 __unsafe_unretained RLMProperty *const prop,
88 __unsafe_unretained RLMSchema *const schema) {
89 RLMValidateValueForProperty(obj, objectSchema, prop);
90 if (!obj || obj == NSNull.null) {
93 if (prop.type == RLMPropertyTypeObject) {
94 Class objectClass = schema[prop.objectClassName].objectClass;
96 NSMutableArray *ret = [[NSMutableArray alloc] init];
98 [ret addObject:coerceToObjectType(el, objectClass, schema)];
102 return coerceToObjectType(obj, objectClass, schema);
107 - (instancetype)initWithValue:(id)value schema:(RLMSchema *)schema {
108 if (!(self = [super init])) {
112 if (!value || value == NSNull.null) {
113 @throw RLMException(@"Must provide a non-nil value.");
116 if (!maybeInitObjectSchemaForUnmanaged(self)) {
117 // Don't populate fields from the passed-in object if we're called
118 // during schema init
122 NSArray *properties = _objectSchema.properties;
123 if (NSArray *array = RLMDynamicCast<NSArray>(value)) {
124 if (array.count > properties.count) {
125 @throw RLMException(@"Invalid array input: more values (%llu) than properties (%llu).",
126 (unsigned long long)array.count, (unsigned long long)properties.count);
129 for (id val in array) {
130 RLMProperty *prop = properties[i++];
131 [self setValue:validatedObjectForProperty(RLMCoerceToNil(val), _objectSchema, prop, schema)
136 // assume our object is an NSDictionary or an object with kvc properties
137 for (RLMProperty *prop in properties) {
138 id obj = RLMValidatedValueForProperty(value, prop.name, _objectSchema.className);
140 // don't set unspecified properties
145 [self setValue:validatedObjectForProperty(RLMCoerceToNil(obj), _objectSchema, prop, schema)
153 id RLMCreateManagedAccessor(Class cls, __unsafe_unretained RLMRealm *realm, RLMClassInfo *info) {
154 RLMObjectBase *obj = [[cls alloc] initWithRealm:realm schema:info->rlmObjectSchema];
159 - (instancetype)initWithRealm:(__unsafe_unretained RLMRealm *const)realm
160 schema:(RLMObjectSchema *)schema {
164 _objectSchema = schema;
169 - (id)valueForKey:(NSString *)key {
170 if (_observationInfo) {
171 return _observationInfo->valueForKey(key);
173 return [super valueForKey:key];
176 // Generic Swift properties can't be dynamic, so KVO doesn't work for them by default
177 - (id)valueForUndefinedKey:(NSString *)key {
178 if (Ivar ivar = _objectSchema[key].swiftIvar) {
179 return RLMCoerceToNil(object_getIvar(self, ivar));
181 return [super valueForUndefinedKey:key];
184 - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
185 value = RLMCoerceToNil(value);
186 RLMProperty *property = _objectSchema[key];
187 if (Ivar ivar = property.swiftIvar) {
188 if (property.array && (!value || [value conformsToProtocol:@protocol(NSFastEnumeration)])) {
189 RLMArray *array = [object_getIvar(self, ivar) _rlmArray];
190 [array removeAllObjects];
193 [array addObjects:validatedObjectForProperty(value, _objectSchema, property,
194 RLMSchema.partialPrivateSharedSchema)];
197 else if (property.optional) {
198 RLMOptionalBase *optional = object_getIvar(self, ivar);
199 optional.underlyingValue = value;
203 [super setValue:value forUndefinedKey:key];
206 // overridden at runtime per-class for performance
207 + (NSString *)className {
208 NSString *className = NSStringFromClass(self);
209 if ([RLMSwiftSupport isSwiftClassName:className]) {
210 className = [RLMSwiftSupport demangleClassName:className];
215 // overridden at runtime per-class for performance
216 + (RLMObjectSchema *)sharedSchema {
217 return [RLMSchema sharedSchemaForClass:self.class];
220 + (void)initializeLinkedObjectSchemas {
221 for (RLMProperty *prop in self.sharedSchema.properties) {
222 if (prop.type == RLMPropertyTypeObject && !RLMSchema.partialPrivateSharedSchema[prop.objectClassName]) {
223 [[RLMSchema classForString:prop.objectClassName] initializeLinkedObjectSchemas];
228 + (Class)objectUtilClass:(BOOL)isSwift {
229 return RLMObjectUtilClass(isSwift);
232 - (NSString *)description
234 if (self.isInvalidated) {
235 return @"[invalid object]";
238 return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
241 - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
243 return @"<Maximum depth exceeded>";
246 NSString *baseClassName = _objectSchema.className;
247 NSMutableString *mString = [NSMutableString stringWithFormat:@"%@ {\n", baseClassName];
249 for (RLMProperty *property in _objectSchema.properties) {
250 id object = RLMObjectBaseObjectForKeyedSubscript(self, property.name);
252 if ([object respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
253 sub = [object descriptionWithMaxDepth:depth - 1];
255 else if (property.type == RLMPropertyTypeData) {
256 static NSUInteger maxPrintedDataLength = 24;
257 NSData *data = object;
258 NSUInteger length = data.length;
259 if (length > maxPrintedDataLength) {
260 data = [NSData dataWithBytes:data.bytes length:maxPrintedDataLength];
262 NSString *dataDescription = [data description];
263 sub = [NSString stringWithFormat:@"<%@ — %lu total bytes>", [dataDescription substringWithRange:NSMakeRange(1, dataDescription.length - 2)], (unsigned long)length];
266 sub = [object description];
268 [mString appendFormat:@"\t%@ = %@;\n", property.name, [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
270 [mString appendString:@"}"];
272 return [NSString stringWithString:mString];
275 - (RLMRealm *)realm {
279 - (RLMObjectSchema *)objectSchema {
280 return _objectSchema;
283 - (BOOL)isInvalidated {
284 // if not unmanaged and our accessor has been detached, we have been deleted
285 return self.class == _objectSchema.accessorClass && !_row.is_attached();
288 - (BOOL)isEqual:(id)object {
289 if (RLMObjectBase *other = RLMDynamicCast<RLMObjectBase>(object)) {
290 if (_objectSchema.primaryKeyProperty) {
291 return RLMObjectBaseAreEqual(self, other);
294 return [super isEqual:object];
298 if (_objectSchema.primaryKeyProperty) {
299 id primaryProperty = [self valueForKey:_objectSchema.primaryKeyProperty.name];
301 // modify the hash of our primary key value to avoid potential (although unlikely) collisions
302 return [primaryProperty hash] ^ 1;
309 + (BOOL)shouldIncludeInDefaultSchema {
310 return RLMIsObjectSubclass(self);
313 + (NSString *)_realmObjectName {
317 - (id)mutableArrayValueForKey:(NSString *)key {
318 id obj = [self valueForKey:key];
319 if ([obj isKindOfClass:[RLMArray class]]) {
322 return [super mutableArrayValueForKey:key];
325 - (void)addObserver:(id)observer
326 forKeyPath:(NSString *)keyPath
327 options:(NSKeyValueObservingOptions)options
328 context:(void *)context {
329 if (!_observationInfo) {
330 _observationInfo = new RLMObservationInfo(self);
332 _observationInfo->recordObserver(_row, _info, _objectSchema, keyPath);
334 [super addObserver:observer forKeyPath:keyPath options:options context:context];
337 - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
338 [super removeObserver:observer forKeyPath:keyPath];
339 if (_observationInfo)
340 _observationInfo->removeObserver();
343 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
344 const char *className = class_getName(self);
345 const char accessorClassPrefix[] = "RLM:Managed";
346 if (!strncmp(className, accessorClassPrefix, sizeof(accessorClassPrefix) - 1)) {
347 if ([class_getSuperclass(self.class) sharedSchema][key]) {
352 return [super automaticallyNotifiesObserversForKey:key];
355 #pragma mark - Thread Confined Protocol Conformance
357 - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
358 Object object(_realm->_realm, *_info->objectSchema, _row);
359 realm::ThreadSafeReference<Object> reference = _realm->_realm->obtain_thread_safe_reference(std::move(object));
360 return std::make_unique<realm::ThreadSafeReference<Object>>(std::move(reference));
363 - (id)objectiveCMetadata {
367 + (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
368 metadata:(__unused id)metadata
369 realm:(RLMRealm *)realm {
370 REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<Object> *>(reference.get()));
371 auto object_reference = static_cast<realm::ThreadSafeReference<Object> *>(reference.get());
373 Object object = realm->_realm->resolve_thread_safe_reference(std::move(*object_reference));
374 if (!object.is_valid()) {
377 NSString *objectClassName = @(object.get_object_schema().name.c_str());
379 return RLMCreateObjectAccessor(realm, realm->_info[objectClassName], object.row().get_index());
384 RLMRealm *RLMObjectBaseRealm(__unsafe_unretained RLMObjectBase *object) {
385 return object ? object->_realm : nil;
388 RLMObjectSchema *RLMObjectBaseObjectSchema(__unsafe_unretained RLMObjectBase *object) {
389 return object ? object->_objectSchema : nil;
392 id RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase *object, NSString *key) {
397 if (object->_realm) {
398 return RLMDynamicGetByName(object, key, false);
401 return [object valueForKey:key];
405 void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase *object, NSString *key, id obj) {
410 if (object->_realm) {
411 RLMDynamicValidatedSet(object, key, obj);
414 [object setValue:obj forKey:key];
419 BOOL RLMObjectBaseAreEqual(RLMObjectBase *o1, RLMObjectBase *o2) {
420 // if not the correct types throw
421 if ((o1 && ![o1 isKindOfClass:RLMObjectBase.class]) || (o2 && ![o2 isKindOfClass:RLMObjectBase.class])) {
422 @throw RLMException(@"Can only compare objects of class RLMObjectBase");
424 // if identical object (or both are nil)
429 if (o1 == nil || o2 == nil) {
432 // if not in realm or differing realms
433 if (o1->_realm == nil || o1->_realm != o2->_realm) {
436 // if either are detached
437 if (!o1->_row.is_attached() || !o2->_row.is_attached()) {
440 // if table and index are the same
441 return o1->_row.get_table() == o2->_row.get_table()
442 && o1->_row.get_index() == o2->_row.get_index();
445 id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) {
447 return [object valueForKey:key];
449 @catch (NSException *e) {
450 if ([e.name isEqualToString:NSUndefinedKeyException]) {
451 @throw RLMException(@"Invalid value '%@' to initialize object of type '%@': missing key '%@'",
452 object, className, key);
458 Class RLMObjectUtilClass(BOOL isSwift) {
459 static Class objectUtilObjc = [RLMObjectUtil class];
460 static Class objectUtilSwift = NSClassFromString(@"RealmSwiftObjectUtil");
461 return isSwift && objectUtilSwift ? objectUtilSwift : objectUtilObjc;
464 @implementation RLMObjectUtil
466 + (NSArray *)ignoredPropertiesForClass:(Class)cls {
467 return [cls ignoredProperties];
470 + (NSArray *)indexedPropertiesForClass:(Class)cls {
471 return [cls indexedProperties];
474 + (NSDictionary *)linkingObjectsPropertiesForClass:(Class)cls {
475 return [cls linkingObjectsProperties];
478 + (NSDictionary *)linkingObjectProperties:(__unused id)object {
482 + (NSArray *)getSwiftProperties:(__unused id)obj {
486 + (NSDictionary *)getOptionalProperties:(__unused id)obj {
490 + (NSArray *)requiredPropertiesForClass:(Class)cls {
491 return [cls requiredProperties];
496 @implementation RLMSwiftPropertyMetadata
498 + (instancetype)metadataForOtherProperty:(NSString *)propertyName {
499 RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
500 md.propertyName = propertyName;
501 md.kind = RLMSwiftPropertyKindOther;
505 + (instancetype)metadataForListProperty:(NSString *)propertyName {
506 RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
507 md.propertyName = propertyName;
508 md.kind = RLMSwiftPropertyKindList;
512 + (instancetype)metadataForLinkingObjectsProperty:(NSString *)propertyName
513 className:(NSString *)className
514 linkedPropertyName:(NSString *)linkedPropertyName {
515 RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
516 md.propertyName = propertyName;
517 md.className = className;
518 md.linkedPropertyName = linkedPropertyName;
519 md.kind = RLMSwiftPropertyKindLinkingObjects;
523 + (instancetype)metadataForOptionalProperty:(NSString *)propertyName type:(RLMPropertyType)type {
524 RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
525 md.propertyName = propertyName;
526 md.propertyType = type;
527 md.kind = RLMSwiftPropertyKindOptional;
531 + (instancetype)metadataForNilLiteralOptionalProperty:(NSString *)propertyName {
532 RLMSwiftPropertyMetadata *md = [RLMSwiftPropertyMetadata new];
533 md.propertyName = propertyName;
534 md.kind = RLMSwiftPropertyKindNilLiteralOptional;