diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h index d90a43b..c63464b 100644 --- a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h +++ b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h @@ -33,7 +33,7 @@ #import #import "NSObject+OSReflectionKit.h" -@interface NSManagedObject (OSReflectionKit) +@interface NSManagedObject (OSReflectionKit) #pragma mark - Properties @@ -81,34 +81,6 @@ /// @name Instantiation Methods ///----------------------------- -/** - Creates an instance from the type of the calling class. - - @return The instance of the created object - @see -objectFromDictionary: - */ -+ (instancetype) objectWithController:(NSFetchedResultsController *) controller __deprecated; - -/** - Creates an instance from the type of the calling class and sets its properties from a `NSDictionay` object. - - @param dictionary The `NSDictionary` object containing the object data. - @return The instance of the created object - @discussion If you have a class that has a property: `NSString` *name, then you can call [CustomClassName objectFromDictionay:@{@"name" : @"Alexandre Santos"}] and it will return an object of the type 'CustomClassName' with the attribute 'name' containing the value 'Alexandre Santos'. - @see -object - */ -+ (instancetype) objectFromDictionary:(NSDictionary *) dictionary withController:(NSFetchedResultsController *) controller __deprecated; - -/** - Creates a `NSArray` instance from the type of the calling class and sets its properties from an array of `NSDictionay` objects. - - @param dicts An array of `NSDictionary` objects containing the objects data. - @return An array of objects from the calling class type. - @deprecated Please use `objectsFromDicts:inManagedObjectContext` instead. - @see -objectFromDictionary: - */ -+ (NSArray *) objectsFromDicts:(NSArray *) dicts withController:(NSFetchedResultsController *) controller __deprecated; - /** Creates a `NSArray` instance from the type of the calling class and sets its properties from an array of `NSDictionay` objects. @@ -170,9 +142,6 @@ @discussion If you have a class that has a property: `NSString` *name, then you can call [CustomClassName objectFromJSON:@"{"name" : "Alexandre Santos"}"] and it will return an object of the type 'CustomClassName' with the attribute 'name' containing the value 'Alexandre Santos'. @see -objectFromDictionary: */ -+ (instancetype) objectFromJSON:(NSString *) jsonString withController:(NSFetchedResultsController *) controller error:(NSError **) error __deprecated; -+ (instancetype) objectFromJSON:(NSString *) jsonString withController:(NSFetchedResultsController *) controller __deprecated; - + (instancetype) objectFromJSON:(NSString *) jsonString inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName error:(NSError **) error; + (instancetype) objectFromJSON:(NSString *) jsonString inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName; + (instancetype) objectFromJSON:(NSString *) jsonString inManagedObjectContext:(NSManagedObjectContext *) context; @@ -185,9 +154,6 @@ @return An array of objects from the calling class type. @see -objectFromJSON: */ -+ (NSArray *)objectsFromJSONArray:(NSString *)jsonArray withController:(NSFetchedResultsController *) controller error:(NSError **) error __deprecated; -+ (NSArray *)objectsFromJSONArray:(NSString *)jsonArray withController:(NSFetchedResultsController *) controller __deprecated; - + (NSArray *)objectsFromJSONArray:(NSString *)jsonArray inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName error:(NSError **) error; + (NSArray *)objectsFromJSONArray:(NSString *)jsonArray inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName ; + (NSArray *)objectsFromJSONArray:(NSString *)jsonArray inManagedObjectContext:(NSManagedObjectContext *) context; diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m index c7a32ae..22df458 100644 --- a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m @@ -6,6 +6,8 @@ // Copyright (c) 2013 iAOS Software. All rights reserved. // +#import + #if ! __has_feature(objc_arc) #error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif @@ -15,12 +17,41 @@ static NSString * const OSReflectionKitCoreDataExtensionsErrorDomain = @"OSReflectionKitCoreDataExtensionsErrorDomain"; +static NSCache *OSRManagedObjectCacheForContext = nil; + @implementation NSManagedObject (OSReflectionKit) ++ (NSCache *)OSRManagedObjectCacheForContext:(NSManagedObjectContext *)context +{ + NSCache *OSRManagedObjectCache = [OSRManagedObjectCacheForContext objectForKey:context]; + + if (!OSRManagedObjectCache) { + OSRManagedObjectCache = [[NSCache alloc] init]; + OSRManagedObjectCache.countLimit = 1024; + [OSRManagedObjectCacheForContext setObject:OSRManagedObjectCache forKey:context]; + } + + return OSRManagedObjectCache; +} + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + OSRManagedObjectCacheForContext = [[NSCache alloc] init]; + [OSRManagedObjectCacheForContext setName:@"OSRManagedObjectCacheForContext"]; + }); +} + static NSManagedObjectContext *_defaultContext = nil; #pragma mark - Class Properties ++ (NSDictionary *)reflectionMapping +{ + return @{}; +} + + (NSString *)entityName { return NSStringFromClass([self class]); @@ -54,7 +85,29 @@ + (NSManagedObjectContext *) defaultManagedObjectContext + (NSEntityDescription *)entityDescription { - return [NSEntityDescription entityForName:[self entityName] inManagedObjectContext:[self defaultManagedObjectContext]]; + NSEntityDescription * __block entityDescription = nil; + [[self defaultManagedObjectContext] performBlockAndWait:^{ + entityDescription = [NSEntityDescription entityForName:[self entityName] inManagedObjectContext:[self defaultManagedObjectContext]]; + }]; + return entityDescription; +} + +#pragma mark - Reflection exceptions + +- (void)reflectionValue:(id)value forUnkownKey:(NSString *)key +{ + @try { + @throw [NSException exceptionWithName:@"OSRValueAssignmentException" reason:@"Could not assign" userInfo:@{@"Key" : key, @"Value" : value ?: [NSNull null]}]; + } + @catch (...) {} +} + +- (void)reflectionMappingError:(NSError *)error withValue:(id)value forKey:(NSString *)propertyName +{ + @try { + @throw [NSException exceptionWithName:@"OSRMappingException" reason:@"Could not map" userInfo:@{@"Key" : propertyName, @"Value" : value ?: [NSNull null], NSUnderlyingErrorKey : error}]; + } + @catch (...) {} } #pragma mark - Instance Properties @@ -72,24 +125,41 @@ - (BOOL) isNew - (BOOL) hasBeenDeleted { - NSManagedObject *managedObjectClone = [[self managedObjectContext] existingObjectWithID:[self objectID] error:nil]; + NSManagedObject * __block managedObjectClone = nil; + + [[self managedObjectContext] performBlockAndWait:^{ + managedObjectClone = [[self managedObjectContext] existingObjectWithID:[self objectID] error:nil]; + }]; + return (managedObjectClone == nil); } #pragma mark - Instanciation Methods ++ (instancetype)reflectionNewInstanceWithDictionary:(NSDictionary *)dictionary +{ + return [self objectFromDictionary:dictionary]; +} + + (instancetype)objectFromDictionary:(NSDictionary *)dictionary { NSAssert([self defaultManagedObjectContext], @"Please register the default managed object context for class: '%@' before using '%s'.", NSStringFromClass([self class]), __PRETTY_FUNCTION__); - - return [self objectFromDictionary:dictionary inManagedObjectContext:[self defaultManagedObjectContext]]; + NSManagedObject * __block managedObject = nil; + [[self defaultManagedObjectContext] performBlockAndWait:^{ + managedObject = [self objectFromDictionary:dictionary inManagedObjectContext:[self defaultManagedObjectContext]]; + }]; + return managedObject; } + (instancetype)objectFromJSON:(NSString *)jsonString { NSAssert([self defaultManagedObjectContext], @"Please register the default managed object context for class: '%@' before using '%s'.", NSStringFromClass([self class]), __PRETTY_FUNCTION__); - - return [self objectFromJSON:jsonString inManagedObjectContext:[self defaultManagedObjectContext]]; + + NSManagedObject * __block managedObject = nil; + [[self defaultManagedObjectContext] performBlockAndWait:^{ + managedObject = [self objectFromJSON:jsonString inManagedObjectContext:[self defaultManagedObjectContext]]; + }]; + return managedObject; } + (instancetype) objectInManagedObjectContext:(NSManagedObjectContext *) context @@ -112,31 +182,46 @@ + (instancetype) objectFromDictionary:(NSDictionary *) dictionary inManagedObjec + (instancetype) objectFromDictionary:(NSDictionary *) dictionary inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName { // Check if the dictionary is not stored yet - id object = [self firstWithDictionary:dictionary inManagedObjectContext:context forEntityName:entityName]; + id __block object = [self firstWithDictionary:dictionary inManagedObjectContext:context forEntityName:entityName]; if(object == nil) { // Create a new object since there is no one like - object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; + [context performBlockAndWait:^{ + object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; + }]; } - + // Try to map the object if there is any dictionary data if([dictionary count] > 0) { - NSError *error = nil; - if(![object mapWithDictionary:dictionary error:&error]) - NSLog(@"Error mapping object: %@", error); + [context performBlockAndWait:^{ + NSError *error = nil; + if(![object mapWithDictionary:dictionary error:&error]) { +#if defined(DEBUG) + NSLog(@"Error mapping object: %@", error); +#endif + } + }]; } - - // Auto-increment - NSError *error = nil; - NSDictionary *autoincrementedFields = [object autoincrementedFieldsDictWithError:&error]; - if (error) - NSLog(@"Error auto-incrementing fields: %@", error); - - if(![object mapWithDictionary:autoincrementedFields error:&error]) - NSLog(@"Error mapping object for auto-increment fields: %@", error); - + + [context performBlockAndWait:^{ + // Auto-increment + NSError *error = nil; + NSDictionary *autoincrementedFields = [object autoincrementedFieldsDictWithError:&error]; + if (error) { +#if defined(DEBUG) + NSLog(@"Error auto-incrementing fields: %@", error); +#endif + } + + if(![object mapWithDictionary:autoincrementedFields error:&error]) { +#if defined(DEBUG) + NSLog(@"Error mapping object for auto-increment fields: %@", error); +#endif + } + }]; + return object; } @@ -147,8 +232,9 @@ + (NSArray *) objectsFromDicts:(NSArray *) dicts inManagedObjectContext:(NSManag { id obj = [self objectFromDictionary:dict inManagedObjectContext:context forEntityName:entityName]; - if(obj) + if(obj) { [objects addObject:obj]; + } } return [objects copy]; @@ -238,13 +324,18 @@ + (NSUInteger) countInManagedObjectContext:(NSManagedObjectContext *) context fo { NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName]; - if(predicate) + if(predicate) { fetchRequest.predicate = predicate; + } - NSError *error = nil; - - NSUInteger count = [context countForFetchRequest:fetchRequest error:&error]; + NSError * __block error = nil; + NSUInteger __block count = 0; + + [context performBlockAndWait:^{ + count = [context countForFetchRequest:fetchRequest error:&error]; + }]; + if(error) { NSLog(@"%@", error); @@ -255,17 +346,60 @@ + (NSUInteger) countInManagedObjectContext:(NSManagedObjectContext *) context fo + (NSUInteger) countUniqueObjectsWithDictionary:(NSDictionary * ) dictionary inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName limit:(NSUInteger) limit { - NSPredicate *predicate = [NSPredicate predicateForUniqueness:[self class] withDictionary:dictionary]; + NSPredicate *predicate = [NSPredicate predicateForUniquenessForClass:[self class] withDictionary:dictionary]; return [self countInManagedObjectContext:context forEntityName:entityName withPredicate:predicate]; } + (instancetype) firstWithDictionary:(NSDictionary * ) dictionary inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName { - id object = nil; - NSArray *objects = [self fetchUniqueObjectsWithDictionary:dictionary inManagedObjectContext:context forEntityName:entityName limit:1]; + NSManagedObject *object = nil; + NSString *predicateForUniquenessFormat = [NSPredicate predicateStringForUniquenessForClass:[self class] withDictionary:dictionary]; + NSString *keyWithPredicateFormat = [NSString stringWithFormat:@"%@ %@", entityName, predicateForUniquenessFormat]; + +#ifdef DEBUG + static NSUInteger cacheHits = 0; + static NSUInteger cacheMisses = 0; + static float cacheHitRatio = 0; +#endif + + NSCache *OSRManagedObjectCache = [self OSRManagedObjectCacheForContext:context]; + + object = [OSRManagedObjectCache objectForKey:keyWithPredicateFormat]; + + if ([object isDeleted]) { + [OSRManagedObjectCache removeObjectForKey:keyWithPredicateFormat]; + object = nil; + } + + if (object) { +#ifdef DEBUG + cacheHits++; + if (0 == cacheHits % 50) { + cacheHitRatio = (float)cacheHits / ((float)cacheHits + (float)cacheMisses); + } +#endif + return object; + } + + NSArray *objects = [self fetchUniqueObjectsWithDictionary:dictionary inManagedObjectContext:context forEntityName:entityName limit:1 predicate:[NSPredicate predicateWithFormat:predicateForUniquenessFormat]]; + if ([objects count] > 0) { object = [objects firstObject]; + + if ([object isDeleted]) { + [OSRManagedObjectCache removeObjectForKey:keyWithPredicateFormat]; + object = nil; + [context performBlockAndWait:^{ + [context save:nil]; + }]; + } + else { +#ifdef DEBUG + cacheMisses++; +#endif + [OSRManagedObjectCache setObject:object forKey:keyWithPredicateFormat]; + } } return object; @@ -273,7 +407,12 @@ + (instancetype) firstWithDictionary:(NSDictionary * ) dictionary inManagedObjec + (NSArray *) fetchUniqueObjectsWithDictionary:(NSDictionary * ) dictionary inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName limit:(NSUInteger) limit { - NSArray *objects = nil; + return [self fetchUniqueObjectsWithDictionary:dictionary inManagedObjectContext:context forEntityName:entityName limit:limit predicate:nil]; +} + ++ (NSArray *) fetchUniqueObjectsWithDictionary:(NSDictionary * ) dictionary inManagedObjectContext:(NSManagedObjectContext *) context forEntityName:(NSString *) entityName limit:(NSUInteger) limit predicate:(NSPredicate *)predicate +{ + NSArray * __block objects = nil; if([dictionary count] > 0) { @@ -283,15 +422,20 @@ + (NSArray *) fetchUniqueObjectsWithDictionary:(NSDictionary * ) dictionary inMa { request.fetchLimit = limit; } - - NSPredicate *predicate = [NSPredicate predicateForUniqueness:[self class] withDictionary:dictionary]; + + if (!predicate) { + predicate = [NSPredicate predicateForUniquenessForClass:[self class] withDictionary:dictionary]; + } + if(predicate) { request.predicate = predicate; + + NSError * __block error = nil; + [context performBlockAndWait:^{ + objects = [context executeFetchRequest:request error:&error]; + }]; } - - NSError *error = nil; - objects = [context executeFetchRequest:request error:&error]; } return objects; @@ -301,22 +445,28 @@ + (NSArray *) fetchWithPredicate:(NSPredicate *) predicate sortDescriptors:(NSAr { NSAssert([self defaultManagedObjectContext], @"Please register the default managed object context for class: '%@' before using '%s'.", NSStringFromClass([self class]), __PRETTY_FUNCTION__); - NSArray *objects = nil; + NSArray * __block objects = nil; NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:[self entityName]]; - if(limit > 0) + if(limit > 0) { request.fetchLimit = limit; + } - if(sortDescriptors) + if(sortDescriptors) { request.sortDescriptors = sortDescriptors; + } - if(predicate) + if(predicate) { request.predicate = predicate; + } - NSError *error = nil; - objects = [[self defaultManagedObjectContext] executeFetchRequest:request error:&error]; - + NSError * __block error = nil; + + [[self defaultManagedObjectContext] performBlockAndWait:^{ + objects = [[self defaultManagedObjectContext] executeFetchRequest:request error:&error]; + }]; + return objects; } @@ -325,91 +475,6 @@ + (NSArray *) fetchWithPredicate:(NSPredicate *) predicate limit:(NSUInteger) li return [self fetchWithPredicate:predicate sortDescriptors:nil limit:limit]; } - -#pragma mark - Deprecated methods - -+ (instancetype) objectWithController:(NSFetchedResultsController *) controller -{ - return [self objectInManagedObjectContext:[controller managedObjectContext] forEntityName:[[[controller fetchRequest] entity] name]]; -} - -+ (instancetype) objectFromDictionary:(NSDictionary *) dictionary withController:(NSFetchedResultsController *) controller -{ - id object = [self objectWithController:controller]; - - NSError *error = nil; - [object mapWithDictionary:dictionary error:&error]; - - if(error) - NSLog(@"Error mapping object: %@", error); - - return object; -} - -+ (NSArray *) objectsFromDicts:(NSArray *) dicts withController:(NSFetchedResultsController *) controller -{ - NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[dicts count]]; - for (NSDictionary *dict in dicts) - { - id obj = [self objectFromDictionary:dict withController:controller]; - - if(obj) - [objects addObject:obj]; - } - - return [objects copy]; -} - -+ (instancetype) objectFromJSON:(NSString *)jsonString withController:(NSFetchedResultsController *) controller -{ - return [self objectFromJSON:jsonString withController:controller error:nil]; -} - -+ (instancetype)objectFromJSON:(NSString *)jsonString withController:(NSFetchedResultsController *) controller error:(NSError **)error -{ - // Convert the JSON text into a dictionary object - NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:error]; - if(dictionary == nil) - { - // Something is wrong with the JSON text - NSLog(@"Invalid json data: %@", *error); - return nil; - } - else if([dictionary isKindOfClass:[NSDictionary class]]) - { - // Load the Profile object from the dictionary - return [self objectFromDictionary:dictionary withController:controller]; - } - - return nil; -} - -+ (NSArray *)objectsFromJSONArray:(NSString *)jsonArray withController:(NSFetchedResultsController *) controller -{ - return [self objectsFromJSONArray:jsonArray withController:controller error:nil]; -} - -+ (NSArray *)objectsFromJSONArray:(NSString *)jsonArray withController:(NSFetchedResultsController *) controller error:(NSError **)error -{ - // Convert the JSON text into a dictionary object - NSData *jsonData = [jsonArray dataUsingEncoding:NSUTF8StringEncoding]; - NSArray *arrayDicts = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:error]; - if(arrayDicts == nil) - { - // Something is wrong with the JSON text - NSLog(@"Invalid json data: %@", *error); - return nil; - } - else if([arrayDicts isKindOfClass:[NSArray class]]) - { - // Load the Profile object from the dictionary - return [self objectsFromDicts:arrayDicts withController:controller]; - } - - return nil; -} - #pragma mark - Persistence Methods - (BOOL)save @@ -431,10 +496,16 @@ - (BOOL) saveWithContext:(NSManagedObjectContext *) context error:(NSError **) e { // Check whether it should auto-increment fields before saving NSDictionary *autoincrementedFields = [self autoincrementedFieldsDictWithError:error]; - if(autoincrementedFields) + if(autoincrementedFields) { [self mapWithDictionary:autoincrementedFields error:error]; - - return [context save:error]; + } + + BOOL __block didSave = NO; + [context performBlockAndWait:^{ + didSave = [context save:error]; + }]; + + return didSave; } #pragma mark - Private Methods diff --git a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.h b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.h index 6aa1407..ce3b1e4 100644 --- a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.h +++ b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.h @@ -10,6 +10,7 @@ @interface NSPredicate (OSReflectionKit) -+ (instancetype) predicateForUniqueness:(Class) klazz withDictionary:(NSDictionary *) dictionary; ++ (NSString *)predicateStringForUniquenessForClass:(Class)klazz withDictionary:(NSDictionary *)dictionary; ++ (instancetype)predicateForUniquenessForClass:(Class)klazz withDictionary:(NSDictionary *)dictionary; @end diff --git a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m index 3e4309d..c3f8b5c 100644 --- a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m @@ -15,23 +15,95 @@ @implementation NSPredicate (OSReflectionKit) -+ (instancetype) predicateForUniqueness:(Class) klazz withDictionary:(NSDictionary *) dictionary ++ (NSString *)predicateStringForUniquenessForClass:(Class)klazz withDictionary:(NSDictionary *)dictionary { - NSPredicate *predicate = nil; - - NSArray *uniqueFields = [klazz uniqueFields]; - - if([uniqueFields count] > 0) - { - NSMutableString *predicateFormat = [[uniqueFields componentsJoinedByString:@" = %@ &&"] mutableCopy]; - [predicateFormat appendString:@" = %@"]; - NSArray *values = [dictionary valuesForPropertyNames:uniqueFields]; - - if([values count] > 0) - predicate = [NSPredicate predicateWithFormat:predicateFormat argumentArray:values]; - } - - return predicate; + NSString *predicateString = nil; + + NSArray *uniqueFields = [klazz uniqueFields]; + + if([uniqueFields count] > 0) + { + NSMutableArray *unmappedUniqueFields = [uniqueFields mutableCopy]; + + if ([(id)klazz respondsToSelector:@selector(reflectionMapping)]) { + Class reflectionMappingClass = klazz; + NSDictionary *reflectionMapping = [reflectionMappingClass reflectionMapping]; + + NSMutableArray *allUnmappedKeysMutable = [[reflectionMapping allValues] mutableCopy]; + + for (NSUInteger index = 0; index < [allUnmappedKeysMutable count]; index++) { + if ((id)[NSNull null] != allUnmappedKeysMutable[index]) { + allUnmappedKeysMutable[index] = [[allUnmappedKeysMutable[index] componentsSeparatedByString:@","] firstObject]; + } + } + + NSSet *allUnmappedKeysSet = [NSSet setWithArray:allUnmappedKeysMutable]; + + for (NSUInteger index = 0; index < [uniqueFields count]; index++) { + id key = uniqueFields[index]; + + if ([allUnmappedKeysSet containsObject:key]) { + id mappedKey = [[reflectionMapping allKeysForObject:uniqueFields[index]] firstObject]; + + if (mappedKey) { + unmappedUniqueFields[index] = mappedKey; + } + } + } + } + + NSMutableArray *values = [[dictionary valuesForPropertyNames:unmappedUniqueFields] mutableCopy]; + + for (NSUInteger index = 0; index < [unmappedUniqueFields count]; index++) { + id value = values[index]; + + if ([NSObject propertyName:uniqueFields[index] isPrimitiveOrNumberPropertyOfClass:klazz] && [value isKindOfClass:[NSString class]]) { + NSString *propertyName = uniqueFields[index]; + NSString *unconvertedValue = value; + NSNumber *convertedValue = [NSObject numberFromNumericValueString:unconvertedValue targetPropertyName:propertyName ofClass:klazz]; + values[index] = convertedValue; + } + } + + NSUInteger valuesCount = [values count]; + + if (valuesCount) { + NSCAssert([uniqueFields count] == valuesCount, @"Count of unique fields (%tu) and values (%tu) don't match", [uniqueFields count], valuesCount); + + NSMutableArray *keyValuePairs = [[NSMutableArray alloc] initWithCapacity:valuesCount]; + + for (NSUInteger index = 0; index < valuesCount; index++) { + id value = values[index]; + + if (value == [NSNull null]) { + value = @"NIL"; + } + else if ([value isKindOfClass:[NSString class]]) { + value = [[NSString alloc] initWithFormat:@"'%@'", value]; + } + + [keyValuePairs addObject:[[NSString alloc] initWithFormat:@"%@ == %@", uniqueFields[index], value]]; + } + + predicateString = [keyValuePairs componentsJoinedByString:@" && "]; + } + } + + return predicateString; +} + + ++ (instancetype)predicateForUniquenessForClass:(Class)klazz withDictionary:(NSDictionary *)dictionary +{ + NSPredicate *predicate = nil; + + NSString *predicateString = [self predicateStringForUniquenessForClass:klazz withDictionary:dictionary]; + + if (predicateString) { + predicate = [NSPredicate predicateWithFormat:predicateString]; + } + + return predicate; } @end diff --git a/OSReflectionKit/AZReflection.h b/OSReflectionKit/AZReflection.h index 19a1bd2..935becb 100755 --- a/OSReflectionKit/AZReflection.h +++ b/OSReflectionKit/AZReflection.h @@ -39,7 +39,7 @@ @interface AZReflection : NSObject extern NSString *const AZReflectionMapperErrorDomain; + (AZReflection *)sharedReflectionMapper; -- (id)reflectionMapWithDictionary:(NSDictionary *)dictionary rootClass:(Class)classReference error:(NSError **)error; +- (id)reflectionMapWithDictionary:(NSDictionary *)dictionary rootClass:(Class)classReference context:(void *)context error:(NSError **)error; - (BOOL) mapObject:(id) object withDictionary:(NSDictionary *)dictionary rootClass:(Class)classReference error:(NSError **)error; @@ -60,6 +60,9 @@ extern NSString *const AZReflectionMapperErrorDomain; // Properties Methods + (NSDictionary *) classProperties; + (Class) classForProperty:(NSString *) propertyName; ++ (BOOL)propertyName:(NSString *)propertyName isPrimitiveOrNumberPropertyOfClass:(Class)instanceClass; ++ (NSNumber *)numberFromNumericValueString:(NSString *)value targetPropertyName:(NSString *)propertyName ofClass:(Class)klass; - (BOOL) setValue:(id) value forProperty:(NSString *) propertyName; - (BOOL) setValue:(id) value forProperty:(NSString *) propertyName error:(NSError **) error; -@end \ No newline at end of file + +@end diff --git a/OSReflectionKit/AZReflection.m b/OSReflectionKit/AZReflection.m index fc849ab..ee6df1b 100755 --- a/OSReflectionKit/AZReflection.m +++ b/OSReflectionKit/AZReflection.m @@ -19,12 +19,6 @@ @interface AZReflection () -// Shortcut NSError factory -static inline NSError *ReflectionMapperError(NSString *errorMessage, ...); - -// Method performs type strict assignment -- (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key propertyClass:(Class)propertyClass error:(NSError **)error; - // Structure encapsulating all information about property class // (can't use) just a Class since we can have primitive classes as well struct property_attributes_t { @@ -38,6 +32,20 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property SEL primitiveValueSelector; }; +@property (nonatomic, readonly) NSNumberFormatter *numberFormatter; +@property (nonatomic, readonly) NSDateFormatter *dateFormatterForShortFormat; +@property (nonatomic, readonly) NSDateFormatter *dateFormatterForMediumFormat; +@property (nonatomic, readonly) NSDateFormatter *dateFormatterForLongFormat; + +// Shortcut NSError factory +static inline NSError *ReflectionMapperError(NSString *errorMessage, ...); + +// Method to extract NSNumbers from numeric values expressed as NSStrings +- (NSNumber *)parsedNumberFromNumericValueString:(NSString *)value attributes:(const struct property_attributes_t * const)attributes; + +// Method performs type strict assignment +- (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key propertyClass:(Class)propertyClass error:(NSError **)error; + // Functions extract information about the property / ivar, so we can be sure that value is the same type as property static inline void GetPropertyClassWrapper(objc_property_t property, struct property_attributes_t *answer); static inline void GetIvarClassWrapper(Ivar ivar, struct property_attributes_t *answer); @@ -51,6 +59,16 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property @implementation AZReflection +@dynamic numberFormatter; +@dynamic dateFormatterForShortFormat; +@dynamic dateFormatterForMediumFormat; +@dynamic dateFormatterForLongFormat; + +static NSNumberFormatter *_numberFormatter = nil; +static NSDateFormatter *_dateFormatterForShortFormat = nil; +static NSDateFormatter *_dateFormatterForMediumFormat = nil; +static NSDateFormatter *_dateFormatterForLongFormat = nil; + NSString *const AZReflectionMapperErrorDomain = @"AZReflectionMapperErrorDomain"; + (AZReflection *)sharedReflectionMapper @@ -63,7 +81,7 @@ + (AZReflection *)sharedReflectionMapper return instance; } -- (id)reflectionMapWithDictionary:(NSDictionary *)dictionary rootClass:(Class)classReference error:(NSError **)error +- (id)reflectionMapWithDictionary:(NSDictionary *)dictionary rootClass:(Class)classReference context:(void *)context error:(NSError **)error { if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]] || !classReference) { if(error) @@ -72,11 +90,31 @@ - (id)reflectionMapWithDictionary:(NSDictionary *)dictionary rootClass:(Class)cl } id instance = nil; - - BOOL classHasProtocolFactoryMethod = class_conformsToProtocol(classReference, @protocol(AZReflectionHint)) && [(id)classReference respondsToSelector:@selector(reflectionNewInstanceWithDictionary:)]; + + BOOL classHasProtocolFactoryMethod = NO; + + for (Class classReferenceHierarchy = classReference; classReferenceHierarchy != Nil; classReferenceHierarchy = class_getSuperclass(classReferenceHierarchy)) { + if (class_conformsToProtocol(classReferenceHierarchy, @protocol(AZReflectionHint))) { + classHasProtocolFactoryMethod = [(id)classReference respondsToSelector:@selector(reflectionNewInstanceWithDictionary:)]; + + if (classHasProtocolFactoryMethod) { + break; + } + } + } + // Can we use class as instance factory? if (classHasProtocolFactoryMethod) { - instance = [classReference reflectionNewInstanceWithDictionary:dictionary]; + Class NSManagedObjectClass = NSClassFromString(@"NSManagedObject"); + if ([classReference isSubclassOfClass:NSManagedObjectClass]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + instance = [(id)classReference performSelector:NSSelectorFromString(@"objectFromDictionary:inManagedObjectContext:") withObject:dictionary withObject:(__bridge id)(context)]; +#pragma clang diagnostic pop + } + else { + instance = [classReference reflectionNewInstanceWithDictionary:dictionary]; + } } else { // Otherwise default to instance = [[classReference alloc] init]; @@ -104,7 +142,7 @@ - (BOOL) mapObject:(id) instance withDictionary:(NSDictionary *)dictionary rootC { mapping = [classReference reflectionMapping]; } - + // Now iterate through all key/value pairs [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { @@ -132,28 +170,30 @@ - (BOOL) mapObject:(id) instance withDictionary:(NSDictionary *)dictionary rootC { // we might have a mapping function NSString *mappingHint = [mapping valueForKey:key]; - BOOL usesTransformer = NO; - NSString *customClassString = nil; - ParseMappingHint(mappingHint, &key, &usesTransformer, &customClassString); - if (usesTransformer && [instance respondsToSelector:@selector(reflectionTranformsValue:forKey:)]) - { - [instance reflectionTranformsValue:obj forKey:key]; - } - else - { - if(![self assignValue:obj instance:instance key:key propertyClass:NSClassFromString(customClassString) error:error]) - { - success = NO; - - if([instance respondsToSelector:@selector(reflectionMappingError:withValue:forKey:)]) - { - [instance reflectionMappingError:*error withValue:obj forKey:key]; - } - } + if ((id)[NSNull null] != mappingHint) { + BOOL usesTransformer = NO; + NSString *customClassString = nil; + ParseMappingHint(mappingHint, &key, &usesTransformer, &customClassString); + if (usesTransformer && [instance respondsToSelector:@selector(reflectionTranformsValue:forKey:)]) + { + [instance reflectionTranformsValue:obj forKey:key]; + } + else + { + if(![self assignValue:obj instance:instance key:key propertyClass:NSClassFromString(customClassString) error:error]) + { + success = NO; + + if([instance respondsToSelector:@selector(reflectionMappingError:withValue:forKey:)]) + { + [instance reflectionMappingError:*error withValue:obj forKey:key]; + } + } + } } } }]; - + return success; } @@ -215,6 +255,107 @@ - (NSDictionary *) dictionaryForObject:(id) instance error:(NSError **)error return [dictionary copy]; } +- (NSNumberFormatter *)numberFormatter +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _numberFormatter = [[NSNumberFormatter alloc] init]; + [_numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; + }); + + return _numberFormatter; +} + +- (NSDateFormatter *)dateFormatterForShortFormat +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _dateFormatterForShortFormat = [[NSDateFormatter alloc] init]; + [_dateFormatterForShortFormat setDateFormat:@"yyyy-MM-dd"]; + }); + + return _dateFormatterForShortFormat; +} + +- (NSDateFormatter *)dateFormatterForMediumFormat +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _dateFormatterForMediumFormat = [[NSDateFormatter alloc] init]; + [_dateFormatterForMediumFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; + }); + + return _dateFormatterForMediumFormat; +} + +- (NSDateFormatter *)dateFormatterForLongFormat +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _dateFormatterForLongFormat = [[NSDateFormatter alloc] init]; + [_dateFormatterForLongFormat setDateFormat:@"yyyy-MM-dd'T'HHmmssZZ"]; + }); + + return _dateFormatterForLongFormat; +} + +- (NSNumber *)parsedNumberFromNumericValueString:(NSString *)value attributes:(const struct property_attributes_t * const)attributes +{ + NSNumber *convertedValue = nil; + + SEL primitiveValueSelector = attributes->primitiveValueSelector; + + // Try native NSString number parsing first + if ([value respondsToSelector:primitiveValueSelector]) + { + NSString *selectorAsString = NSStringFromSelector(primitiveValueSelector); + convertedValue = [value valueForKeyPath:selectorAsString]; + } + else + { + convertedValue = [self.numberFormatter numberFromString:value]; + } + + if (!convertedValue) + { + convertedValue = @0; + } + + return convertedValue; +} + +- (NSDate *)dateFromValue:(id)value +{ + NSDateFormatter* formatter = nil; + NSDate *date = nil; + + if ([value isKindOfClass:[NSNumber class]]) + { + date = [NSDate dateWithTimeIntervalSince1970:[value doubleValue]]; + } + else if ([value isKindOfClass:[NSString class]]) + { + if ([value length]) { + NSString *dateString = value; + + if (8 <= [value length] && [value length] <= 10) + formatter = self.dateFormatterForShortFormat; + else if ([value length] <= 19) + formatter = self.dateFormatterForMediumFormat; + else + { + // Ruby on Rails compatibility for date formatted like: 2011-07-20T23:59:00-07:00 + dateString = [dateString stringByReplacingOccurrencesOfString:@":" withString:@""]; + formatter = self.dateFormatterForLongFormat; + } + + date = [formatter dateFromString:dateString]; + } + } + + return date; +} + - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key propertyClass:(Class)propertyClass error:(NSError **)error { const char *keyChar = [key UTF8String]; @@ -223,13 +364,19 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property BOOL instanceImplementsUnknownKey = [instance respondsToSelector:@selector(reflectionValue:forUnkownKey:)]; BOOL instanceImplementsMappingError = [instance respondsToSelector:@selector(reflectionMappingError:withValue:forKey:)]; - void(^assignmentBlock)(struct property_attributes_t attributes) = ^(struct property_attributes_t attributes){ + void(^assignmentBlock)(const struct property_attributes_t * const attributes) = ^(const struct property_attributes_t * const attributes){ + + void *context = NULL; + + if ([instance isKindOfClass:NSClassFromString(@"NSManagedObject")]) { + context = (__bridge void *)([instance performSelector:@selector(managedObjectContext)]); + } // Actual value assigning happens here if ([value isKindOfClass:[NSDictionary class]]) { if (propertyClass) { // it's a custom class - [instance setValue:[self reflectionMapWithDictionary:value rootClass:propertyClass error:error] forKey:key]; + [instance setValue:[self reflectionMapWithDictionary:value rootClass:propertyClass context:context error:error] forKey:key]; } else { [instance setValue:value forKey:key]; } @@ -239,13 +386,13 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property // it's an array of custom classes NSMutableArray *array = [NSMutableArray arrayWithCapacity:((NSArray *)value).count]; for (id subvalue in value) { - [array addObject:[self reflectionMapWithDictionary:subvalue rootClass:propertyClass error:error]]; + [array addObject:[self reflectionMapWithDictionary:subvalue rootClass:propertyClass context:context error:error]]; } id objValue = array; // Convert the NSArray into NSSet according to the property type - if([[[instance class] classForProperty:key] isSubclassOfClass:[NSSet class]]) + if ([[[instance class] classForProperty:key] isSubclassOfClass:[NSSet class]]) { objValue = [NSSet setWithArray:array]; } @@ -255,7 +402,7 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property id objValue = value; // Convert the NSArray into NSSet according to the property type - if([[[instance class] classForProperty:key] isSubclassOfClass:[NSSet class]]) + if ([[[instance class] classForProperty:key] isSubclassOfClass:[NSSet class]]) { objValue = [NSSet setWithArray:value]; } @@ -264,42 +411,51 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property } } else { // check types - if (attributes.primitive && [value isKindOfClass:[NSValue class]]) { - // primitive values are expected to be wrapped into NSNumber or NSValue + if (attributes->primitive) + { + if ([value isKindOfClass:[NSValue class]]) + { + // primitive values are expected to be wrapped into NSNumber or NSValue + [instance setValue:value forKey:key]; + } + else if ([value isKindOfClass:[NSString class]]) + { + NSNumber *numberValue = [self parsedNumberFromNumericValueString:value attributes:attributes]; + [instance setValue:numberValue forKey:key]; + } + else if (!value) + { + // primitive values are expected to be wrapped into NSNumber or NSValue + [instance setValue:@0 forKey:key]; + } + else + { + if (error) { + *error = ReflectionMapperError(@"Could not parse value: %@", value); + } + [instance setValue:nil forKey:key]; + } + } + else if ([value isKindOfClass:[NSString class]] && [attributes->classReference isSubclassOfClass:[NSNumber class]]) + { + NSNumber *numberValue = [self parsedNumberFromNumericValueString:value attributes:attributes]; + [instance setValue:numberValue forKey:key]; + } + else if (!value || [value isKindOfClass:attributes->classReference]) + { [instance setValue:value forKey:key]; - } else if (!value && attributes.primitive) { - // primitive values are expected to be wrapped into NSNumber or NSValue - [instance setValue:@0 forKey:key]; - } else if (!value || [value isKindOfClass:attributes.classReference]) { + } + else if ([[NSDate class] isEqual:attributes->classReference]) + { + NSDate *date = [self dateFromValue:value]; + [instance setValue:date forKey:key]; + } + else if ([[NSURL class] isEqual:attributes->classReference] && [value isKindOfClass:[NSString class]]) { + NSURL *url = [NSURL URLWithString:value]; + [instance setValue:url forKey:key]; + } + else if ([instance isKindOfClass:NSClassFromString(@"NSManagedObject")]) { [instance setValue:value forKey:key]; - } else if ([NSDate class] == attributes.classReference) { - NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; - - if ([value isKindOfClass:[NSNumber class]]) - { - NSDate* date; - date = [NSDate dateWithTimeIntervalSince1970:[value doubleValue]]; - - [instance setValue:date forKey:key]; - } - else if ([value isKindOfClass:[NSString class]]) - { - NSString *dateString = value; - - if ([value length] == 10) - [formatter setDateFormat:@"yyyy-MM-dd"]; - else if ([value length] <= 19) - [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; - else - { - // Ruby on Rails compatibility for date formatted like: 2011-07-20T23:59:00-07:00 - dateString = [value stringByReplacingOccurrencesOfString:@":" withString:@""]; - [formatter setDateFormat:@"yyyy-MM-dd'T'HHmmssZZ"]; - } - - [instance setValue:[formatter dateFromString:dateString] forKey:key]; - } - } } }; @@ -311,7 +467,7 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property GetPropertyClassWrapper(property, &attributes); if (attributes.valid && !attributes.readonly) { - assignmentBlock(attributes); + assignmentBlock(&attributes); return YES; } else if(instanceImplementsMappingError) @@ -325,7 +481,7 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property if (ivar) { GetIvarClassWrapper(ivar, &attributes); if (attributes.valid) { - assignmentBlock(attributes); + assignmentBlock(&attributes); return YES; } else if(instanceImplementsMappingError) @@ -595,7 +751,7 @@ + (NSDictionary *) classProperties + (id)reflectionMapWithDictionary:(NSDictionary *)dictionary error:(NSError **)error { - return [[AZReflection sharedReflectionMapper] reflectionMapWithDictionary:dictionary rootClass:[self class] error:error]; + return [[AZReflection sharedReflectionMapper] reflectionMapWithDictionary:dictionary rootClass:[self class] context:NULL error:error]; } - (BOOL) mapWithDictionary:(NSDictionary *)dictionary @@ -633,6 +789,55 @@ + (Class) classForProperty:(NSString *) propertyName return nil; } ++ (BOOL)propertyName:(NSString *)propertyName isPrimitiveOrNumberPropertyOfClass:(Class)instanceClass +{ + const char *keyChar = [propertyName UTF8String]; + objc_property_t property = class_getProperty(instanceClass, keyChar); + struct property_attributes_t attributes = {0}; + + if (property) { + GetPropertyClassWrapper(property, &attributes); + if (attributes.valid) { + return attributes.primitive || attributes.classReference == [NSNumber class]; + } + } + + Ivar ivar = class_getInstanceVariable(instanceClass, keyChar); + if (ivar) { + GetIvarClassWrapper(ivar, &attributes); + if (attributes.valid) { + return attributes.primitive || attributes.classReference == [NSNumber class]; + } + } + + return NO; +} + ++ (NSNumber *)numberFromNumericValueString:(NSString *)value targetPropertyName:(NSString *)propertyName ofClass:(Class)klass +{ + const char *keyChar = [propertyName UTF8String]; + + // Check for property + objc_property_t property = class_getProperty(klass, keyChar); + struct property_attributes_t attributes; + if (property) { + GetPropertyClassWrapper(property, &attributes); + if (attributes.valid) { + return [[AZReflection sharedReflectionMapper] parsedNumberFromNumericValueString:value attributes:&attributes]; + } + } + + Ivar ivar = class_getInstanceVariable(klass, keyChar); + if (ivar) { + GetIvarClassWrapper(ivar, &attributes); + if (attributes.valid) { + return [[AZReflection sharedReflectionMapper] parsedNumberFromNumericValueString:value attributes:&attributes]; + } + } + + return nil; +} + - (BOOL) setValue:(id) value forProperty:(NSString *) propertyName { return [self setValue:value forProperty:propertyName error:nil]; diff --git a/OSReflectionKit/AZReflectionHint.h b/OSReflectionKit/AZReflectionHint.h index 319007a..00c36c5 100755 --- a/OSReflectionKit/AZReflectionHint.h +++ b/OSReflectionKit/AZReflectionHint.h @@ -16,7 +16,7 @@ /** Custom factory method, should return fully initialized instance of the class */ -+ (id)reflectionNewInstanceWithDictionary:(NSDictionary *)dictionary; ++ (instancetype)reflectionNewInstanceWithDictionary:(NSDictionary *)dictionary; /** Returns dictionary containing hints for the mapping process diff --git a/OSReflectionKit/NSObject+OSReflectionKit.h b/OSReflectionKit/NSObject+OSReflectionKit.h index 5a781f0..7e523ff 100644 --- a/OSReflectionKit/NSObject+OSReflectionKit.h +++ b/OSReflectionKit/NSObject+OSReflectionKit.h @@ -35,7 +35,49 @@ // This category allows any NSObject to be instantiated using an NSDictiony object. Also adds some reflection methods to the NSObject, like listing properties and getting the property type -@interface NSObject (OSReflectionKit) +// The following macros are quite useful for building the +[reflectionMapping] dictionary and to verify the incoming property name in -[reflectionTranformsValue:forKey:]. + +/** + Builds an NSString to express a key path. + + @param OBJ Root object or object type. Used as a template only. + @param PATH A chain of properties + + @return NSString expressing a key path reachable from some instance of the same type of the root object + + @example OSRKeyPath(self, view.superview) + @example OSRKeyPath(UIViewController *, view.superview) + */ +#define OSRKeyPath(OBJ, PATH) ((void)(0 && ((void)({__typeof(OBJ) OSR_ghost_obj; OSR_ghost_obj.PATH;}), 0)), @#PATH) + +/** + Marks a key path as needing to be transformed with -[reflectionTransformsValue:forKey:] when creating from a dictionary. It is suggested to be used in tandem with the OSRKeyPath macro. + + @param KEYPATH A key path + + @return A string flagging the key path as needing transformation + + @example OSRTransform(OSRKeyPath(TYSUser*, picture)) + */ +#define OSRTransform(KEYPATH) ([(KEYPATH) stringByAppendingString:@",*"]) + +/** + Marks a key path as needing instantiation with -[objectFromDictionary:] when the parent ovject is created from a dictionary. It is suggested to be used in tandem with the OSRKeyPath macro. + + @param KEYPATH A key path + @param TARGET_CLASS_NAME The target class name + + @return A string flagging the key path as needing to be instantiated with -[objectFromDictionary:] + + @example OSRAdapt(OSRKeyPath(TYSUser*, xp), TYSXP) + */ +#define OSRAdapt(KEYPATH, TARGET_CLASS_NAME) ((void)(0 && ((void)({__typeof(TARGET_CLASS_NAME) *OSR_ghost_obj __attribute__((unused));}), 0)), [(KEYPATH) stringByAppendingString:@",<"#TARGET_CLASS_NAME">"]) + + +@interface NSObject (OSReflectionKit) +#ifdef TROUBLE + +#endif ///----------------------------- /// @name Instantiation Methods diff --git a/OSReflectionKit/NSObject+OSReflectionKit.m b/OSReflectionKit/NSObject+OSReflectionKit.m index 13f43d0..7f11235 100644 --- a/OSReflectionKit/NSObject+OSReflectionKit.m +++ b/OSReflectionKit/NSObject+OSReflectionKit.m @@ -23,7 +23,7 @@ + (instancetype) object return [[self alloc] init]; } -+ (id) objectFromDictionary:(NSDictionary *) dictionary ++ (instancetype) objectFromDictionary:(NSDictionary *) dictionary { return [self reflectionMapWithDictionary:dictionary error:nil]; } @@ -322,6 +322,8 @@ - (NSString *) fullDescription return [[self dictionary] description]; } + +#ifdef TROUBLE #pragma mark - NSCopying implementation - (id)copyWithZone:(NSZone *)zone @@ -362,4 +364,6 @@ - (id)initWithCoder:(NSCoder *)decoder return self; } +#endif + @end \ No newline at end of file