From 196ea27b0da09ec31e100ce24a03db38d6b8925e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Fri, 9 May 2014 12:37:19 -0300 Subject: [PATCH 01/11] =?UTF-8?q?=E2=80=A2=20Statically=20allocate=20NSDat?= =?UTF-8?q?eFormatters,=20since=20they=20are=20expensive=20to=20create=20a?= =?UTF-8?q?nd=20are=20likely=20to=20be=20reused=20with=20some=20frequency.?= =?UTF-8?q?=20Instantiate=20them=20lazily=20however.=20=E2=80=A2=20Check?= =?UTF-8?q?=20whether=20the=20destination=20property=20is=20primitive=20or?= =?UTF-8?q?=20NSNumber-derived,=20and=20attempt=20to=20parse=20JSON=20stri?= =?UTF-8?q?ng=20values=20into=20NSNumbers=20if=20so.=20=E2=80=A2=20Also,?= =?UTF-8?q?=20pass=20property=5Fattributes=5Ft=20by=20reference=20instead?= =?UTF-8?q?=20of=20copying=20it.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OSReflectionKit/AZReflection.m | 211 ++++++++++++++++++++++++++------- 1 file changed, 165 insertions(+), 46 deletions(-) diff --git a/OSReflectionKit/AZReflection.m b/OSReflectionKit/AZReflection.m index fc849ab..4a0a943 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 @@ -215,6 +233,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,7 +342,7 @@ - (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){ // Actual value assigning happens here if ([value isKindOfClass:[NSDictionary class]]) { @@ -245,7 +364,7 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property 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 +374,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 +383,42 @@ - (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 - [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]) { + 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 + { + *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 ([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]; - } - + } + else if ([[NSDate class] isEqual:attributes->classReference]) + { + NSDate *date = [self dateFromValue:value]; + [instance setValue:date forKey:key]; } } }; @@ -311,7 +430,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 +444,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) From d16e63d8aa2a7e54d6232c0d102c6f1406e6f6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Fri, 9 May 2014 13:04:09 -0300 Subject: [PATCH 02/11] =?UTF-8?q?=E2=80=A2=20A=20trio=20of=20useful=20macr?= =?UTF-8?q?os=20to=20help=20handling=20the=20reflection=20mappings=20and?= =?UTF-8?q?=20transforms.=20-=20OSRKeyPath=20is=20inspired=20by=20EXTKeyPa?= =?UTF-8?q?thCoding=20(from=20libextobjc),=20however=20it=20does=20away=20?= =?UTF-8?q?with=20the=20leading=20'@'=20to=20avoid=20hidden=20calls=20to?= =?UTF-8?q?=20+[NSString=20stringWithUTF8String:],=20simplifies=20it=20to?= =?UTF-8?q?=20always=20take=20(instance,=20key.path)=20arguments,=20and=20?= =?UTF-8?q?expands=20it=20to=20take=20a=20(class)=20type=20instead=20of=20?= =?UTF-8?q?an=20existing=20instance,=20making=20it=20usable=20in=20class?= =?UTF-8?q?=20methods=20without=20resorting=20to=20clunky=20workarounds.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here's an example: + (NSDictionary *)reflectionMapping { return @{ @"id" : OSRKeyPath(AUser*, Id), @"photos" : OSRTransform(OSRKeyPath(AUser*, pictures)), @"whatevs" : OSRAdapt(OSRKeyPath(AUser*, whatever), AWhateverClass) }; } - (void)reflectionTranformsValue:(id)value forKey:(NSString *)propertyName { if ([propertyName isEqual:OSRKeyPath(self, pictures)]) { self.pictures = [APicture objectsFromDicts:value]; } } --- OSReflectionKit/NSObject+OSReflectionKit.h | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/OSReflectionKit/NSObject+OSReflectionKit.h b/OSReflectionKit/NSObject+OSReflectionKit.h index 5a781f0..eae6ecd 100644 --- a/OSReflectionKit/NSObject+OSReflectionKit.h +++ b/OSReflectionKit/NSObject+OSReflectionKit.h @@ -35,6 +35,45 @@ // 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 +// 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_ ## __COUNTER__; OSR_ghost_ ## __COUNTER__.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_ ## __COUNTER__ __attribute__((unused));}), 0)), [(KEYPATH) stringByAppendingString:@",<"#TARGET_CLASS_NAME">"]) + + @interface NSObject (OSReflectionKit) ///----------------------------- From 758a83eaff735136b6392baaf1cda17535db6018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Braga?= Date: Fri, 4 Jul 2014 10:28:46 -0300 Subject: [PATCH 03/11] =?UTF-8?q?Usage=20of=20=5F=5FCOUNTER=5F=5F=20was=20?= =?UTF-8?q?completely=20wrong.=20Fixit=E2=84=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OSReflectionKit/NSObject+OSReflectionKit.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OSReflectionKit/NSObject+OSReflectionKit.h b/OSReflectionKit/NSObject+OSReflectionKit.h index eae6ecd..58f6de5 100644 --- a/OSReflectionKit/NSObject+OSReflectionKit.h +++ b/OSReflectionKit/NSObject+OSReflectionKit.h @@ -48,7 +48,7 @@ @example OSRKeyPath(self, view.superview) @example OSRKeyPath(UIViewController *, view.superview) */ -#define OSRKeyPath(OBJ, PATH) ((void)(0 && ((void)({__typeof(OBJ) OSR_ghost_ ## __COUNTER__; OSR_ghost_ ## __COUNTER__.PATH;}), 0)), @#PATH) +#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. @@ -71,7 +71,7 @@ @example OSRAdapt(OSRKeyPath(TYSUser*, xp), TYSXP) */ -#define OSRAdapt(KEYPATH, TARGET_CLASS_NAME) ((void)(0 && ((void)({__typeof(TARGET_CLASS_NAME) *OSR_ghost_ ## __COUNTER__ __attribute__((unused));}), 0)), [(KEYPATH) stringByAppendingString:@",<"#TARGET_CLASS_NAME">"]) +#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) From 7313c10e9832009bda0d929d21894d374bc95064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Thu, 10 Jul 2014 14:39:52 -0300 Subject: [PATCH 04/11] =?UTF-8?q?Improve=20Core=20Data=20support=20a=20bit?= =?UTF-8?q?.=20=E2=80=A2=20Address=20potential=20differences=20between=20e?= =?UTF-8?q?xternal=20and=20internal=20names=20for=20uniquing=20properties?= =?UTF-8?q?=20=E2=80=A2=20Handle=20parsing=20of=20numbers=20that=20are=20t?= =?UTF-8?q?ransmitted=20as=20strings=20at=20the=20uniquing=20predicate=20?= =?UTF-8?q?=E2=80=A2=20Trust=20key-value=20coding=20when=20the=20instance?= =?UTF-8?q?=20is=20a=20NSManagedObject=20(enables=20value=20transforms)=20?= =?UTF-8?q?=E2=80=A2=20Disable=20the=20NSCoding=20default=20implementation?= =?UTF-8?q?=20on=20NSObject(OSReflectionKit)=20that=20break=20NSManagedObj?= =?UTF-8?q?ect=20subclasses=20that=20are=20unarchived=20from=20the=20persi?= =?UTF-8?q?stent=20store=20for=20not=20using=20their=20designated=20initia?= =?UTF-8?q?lisers.=20Since=20the=20NSCopying=20implementation=20was=20also?= =?UTF-8?q?=20troublesome=20for=20interfering=20with=20Core=20Foundation?= =?UTF-8?q?=20types=20(like=20CGPath=20on=20Core=20Animation=20APIs),=20I?= =?UTF-8?q?=20just=20disabled=20them=20both.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NSManagedObject+OSReflectionKit.h | 2 +- .../NSManagedObject+OSReflectionKit.m | 29 +++++++- .../NSPredicate+OSReflectionKit.m | 46 +++++++++++- OSReflectionKit/AZReflection.h | 5 +- OSReflectionKit/AZReflection.m | 73 ++++++++++++++++++- OSReflectionKit/AZReflectionHint.h | 2 +- OSReflectionKit/NSObject+OSReflectionKit.h | 5 +- OSReflectionKit/NSObject+OSReflectionKit.m | 4 + 8 files changed, 152 insertions(+), 14 deletions(-) diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h index d90a43b..c32b018 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 diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m index c7a32ae..bf68db6 100644 --- a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m @@ -57,6 +57,24 @@ + (NSEntityDescription *)entityDescription return [NSEntityDescription entityForName:[self entityName] inManagedObjectContext:[self defaultManagedObjectContext]]; } +#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 - (BOOL) isSaved @@ -78,6 +96,11 @@ - (BOOL) hasBeenDeleted #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__); @@ -288,10 +311,10 @@ + (NSArray *) fetchUniqueObjectsWithDictionary:(NSDictionary * ) dictionary inMa if(predicate) { request.predicate = predicate; + + NSError *error = nil; + objects = [context executeFetchRequest:request error:&error]; } - - NSError *error = nil; - objects = [context executeFetchRequest:request error:&error]; } return objects; diff --git a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m index 3e4309d..67ac732 100644 --- a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m @@ -23,15 +23,53 @@ + (instancetype) predicateForUniqueness:(Class) klazz withDictionary:(NSDictiona if([uniqueFields count] > 0) { - NSMutableString *predicateFormat = [[uniqueFields componentsJoinedByString:@" = %@ &&"] mutableCopy]; + 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++) { + 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; + } + } + + NSMutableString *predicateFormat = [[uniqueFields componentsJoinedByString:@" = %@ && "] mutableCopy]; [predicateFormat appendString:@" = %@"]; - NSArray *values = [dictionary valuesForPropertyNames:uniqueFields]; - + if([values count] > 0) predicate = [NSPredicate predicateWithFormat:predicateFormat argumentArray:values]; } return predicate; } - @end diff --git a/OSReflectionKit/AZReflection.h b/OSReflectionKit/AZReflection.h index 19a1bd2..21b1235 100755 --- a/OSReflectionKit/AZReflection.h +++ b/OSReflectionKit/AZReflection.h @@ -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 4a0a943..0fba75d 100755 --- a/OSReflectionKit/AZReflection.m +++ b/OSReflectionKit/AZReflection.m @@ -90,8 +90,19 @@ - (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]; @@ -122,7 +133,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) { @@ -420,6 +431,13 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property 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]; + } } }; @@ -752,6 +770,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 58f6de5..7e523ff 100644 --- a/OSReflectionKit/NSObject+OSReflectionKit.h +++ b/OSReflectionKit/NSObject+OSReflectionKit.h @@ -74,7 +74,10 @@ #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) +@interface NSObject (OSReflectionKit) +#ifdef TROUBLE + +#endif ///----------------------------- /// @name Instantiation Methods diff --git a/OSReflectionKit/NSObject+OSReflectionKit.m b/OSReflectionKit/NSObject+OSReflectionKit.m index 13f43d0..d0504bf 100644 --- a/OSReflectionKit/NSObject+OSReflectionKit.m +++ b/OSReflectionKit/NSObject+OSReflectionKit.m @@ -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 From 64667a8f98f4a558498ff8887de0d53f583acd56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Fri, 11 Jul 2014 08:52:26 -0300 Subject: [PATCH 05/11] =?UTF-8?q?=E2=80=A2=20Allow=20mapping=20to=20NSNull?= =?UTF-8?q?=20to=20mean=20"do=20not=20attempt=20to=20map=20this=20value".?= =?UTF-8?q?=20=E2=80=A2=20Minor=20change=20for=20consistency=20between=20h?= =?UTF-8?q?eader=20and=20implementation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NSPredicate+OSReflectionKit.m | 4 +- OSReflectionKit/AZReflection.m | 40 ++++++++++--------- OSReflectionKit/NSObject+OSReflectionKit.m | 2 +- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m index 67ac732..59481ce 100644 --- a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m @@ -32,7 +32,9 @@ + (instancetype) predicateForUniqueness:(Class) klazz withDictionary:(NSDictiona NSMutableArray *allUnmappedKeysMutable = [[reflectionMapping allValues] mutableCopy]; for (NSUInteger index = 0; index < [allUnmappedKeysMutable count]; index++) { - allUnmappedKeysMutable[index] = [[allUnmappedKeysMutable[index] componentsSeparatedByString:@","] firstObject]; + if ((id)[NSNull null] != allUnmappedKeysMutable[index]) { + allUnmappedKeysMutable[index] = [[allUnmappedKeysMutable[index] componentsSeparatedByString:@","] firstObject]; + } } NSSet *allUnmappedKeysSet = [NSSet setWithArray:allUnmappedKeysMutable]; diff --git a/OSReflectionKit/AZReflection.m b/OSReflectionKit/AZReflection.m index 0fba75d..a7be18f 100755 --- a/OSReflectionKit/AZReflection.m +++ b/OSReflectionKit/AZReflection.m @@ -161,28 +161,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; } diff --git a/OSReflectionKit/NSObject+OSReflectionKit.m b/OSReflectionKit/NSObject+OSReflectionKit.m index d0504bf..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]; } From e2b88351ba3e5fca9ea45e97175182b077878ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Thu, 16 Apr 2015 08:55:04 -0300 Subject: [PATCH 06/11] Remove deprecated methods --- .../NSManagedObject+OSReflectionKit.h | 34 -------- .../NSManagedObject+OSReflectionKit.m | 85 ------------------- 2 files changed, 119 deletions(-) diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h index c32b018..c63464b 100644 --- a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h +++ b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.h @@ -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 bf68db6..648e0b0 100644 --- a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m @@ -348,91 +348,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 From b23c5eb354bbe2424cc2621bd9869f4e7f415b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Thu, 16 Apr 2015 09:07:07 -0300 Subject: [PATCH 07/11] Default implementation for +[reflectionMapping] --- OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m index 648e0b0..76bb692 100644 --- a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m @@ -21,6 +21,11 @@ @implementation NSManagedObject (OSReflectionKit) #pragma mark - Class Properties ++ (NSDictionary *)reflectionMapping +{ + return @{}; +} + + (NSString *)entityName { return NSStringFromClass([self class]); From 4a1c21ba26e63cc7625a5a92ada951aa14b82280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Thu, 16 Apr 2015 09:20:34 -0300 Subject: [PATCH 08/11] Expose the predicate string and adjust method names for better readability --- .../NSManagedObject+OSReflectionKit.m | 24 ++++++-- .../NSPredicate+OSReflectionKit.h | 3 +- .../NSPredicate+OSReflectionKit.m | 60 ++++++++++++++----- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m index 76bb692..1149b3e 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 @@ -283,14 +285,16 @@ + (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]; + NSArray *objects = [self fetchUniqueObjectsWithDictionary:dictionary inManagedObjectContext:context forEntityName:entityName limit:1 predicate:[NSPredicate predicateWithFormat:predicateForUniquenessFormat]]; + if ([objects count] > 0) { object = [objects firstObject]; @@ -301,7 +305,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) { @@ -311,8 +320,11 @@ + (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; 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 59481ce..c3f8b5c 100644 --- a/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSPredicate+OSReflectionKit.m @@ -15,14 +15,14 @@ @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) - { + NSString *predicateString = nil; + + NSArray *uniqueFields = [klazz uniqueFields]; + + if([uniqueFields count] > 0) + { NSMutableArray *unmappedUniqueFields = [uniqueFields mutableCopy]; if ([(id)klazz respondsToSelector:@selector(reflectionMapping)]) { @@ -65,13 +65,45 @@ + (instancetype) predicateForUniqueness:(Class) klazz withDictionary:(NSDictiona } } - NSMutableString *predicateFormat = [[uniqueFields componentsJoinedByString:@" = %@ && "] mutableCopy]; - [predicateFormat appendString:@" = %@"]; + 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; +} + - if([values count] > 0) - predicate = [NSPredicate predicateWithFormat:predicateFormat argumentArray:values]; - } - - return predicate; ++ (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 From cfea648444c9b92aaea275bed71920027d4ba413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Thu, 16 Apr 2015 09:36:35 -0300 Subject: [PATCH 09/11] Simple but effective caching scheme based on the unique predicate string recently exposed as the key. Saves *a lot* of trips to Core Data-land. --- .../NSManagedObject+OSReflectionKit.m | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m index 1149b3e..a986274 100644 --- a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m @@ -17,8 +17,32 @@ 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 @@ -293,11 +317,52 @@ + (instancetype) firstWithDictionary:(NSDictionary * ) dictionary inManagedObjec { 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; From 64eb5e198919327f9531b2c41f838c8bcc265627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Thu, 16 Apr 2015 09:45:52 -0300 Subject: [PATCH 10/11] Protect managed object context access using -[performBlockAndWait:] --- .../NSManagedObject+OSReflectionKit.m | 129 ++++++++++++------ 1 file changed, 90 insertions(+), 39 deletions(-) diff --git a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m index a986274..22df458 100644 --- a/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m +++ b/OSReflectionKit+CoreData/NSManagedObject+OSReflectionKit.m @@ -85,7 +85,11 @@ + (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 @@ -121,7 +125,12 @@ - (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); } @@ -135,15 +144,22 @@ + (instancetype)reflectionNewInstanceWithDictionary:(NSDictionary *)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 @@ -166,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; } @@ -201,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]; @@ -292,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); @@ -394,8 +431,10 @@ + (NSArray *) fetchUniqueObjectsWithDictionary:(NSDictionary * ) dictionary inMa { request.predicate = predicate; - NSError *error = nil; - objects = [context executeFetchRequest:request error:&error]; + NSError * __block error = nil; + [context performBlockAndWait:^{ + objects = [context executeFetchRequest:request error:&error]; + }]; } } @@ -406,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; } @@ -451,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 From 62f4dd56a8be9c40c082f4986d2614f1b64292ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Braga?= Date: Thu, 16 Apr 2015 09:56:28 -0300 Subject: [PATCH 11/11] Better support for reflection mapping of managed objects in different contexts --- OSReflectionKit/AZReflection.h | 2 +- OSReflectionKit/AZReflection.m | 29 +++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/OSReflectionKit/AZReflection.h b/OSReflectionKit/AZReflection.h index 21b1235..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; diff --git a/OSReflectionKit/AZReflection.m b/OSReflectionKit/AZReflection.m index a7be18f..ee6df1b 100755 --- a/OSReflectionKit/AZReflection.m +++ b/OSReflectionKit/AZReflection.m @@ -81,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) @@ -105,7 +105,16 @@ - (id)reflectionMapWithDictionary:(NSDictionary *)dictionary rootClass:(Class)cl // 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]; @@ -357,11 +366,17 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property 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]; } @@ -371,7 +386,7 @@ - (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; @@ -415,7 +430,9 @@ - (BOOL)assignValue:(id)value instance:(id)instance key:(NSString *)key property } else { - *error = ReflectionMapperError(@"Could not parse value: %@", value); + if (error) { + *error = ReflectionMapperError(@"Could not parse value: %@", value); + } [instance setValue:nil forKey:key]; } } @@ -734,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