/* Copyright (c) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // GTLRuntimeCommon.m // #include #import "GTLRuntimeCommon.h" #import "GTLDateTime.h" #import "GTLObject.h" #import "GTLUtilities.h" static NSString *const kReturnClassKey = @"returnClass"; static NSString *const kContainedClassKey = @"containedClass"; static NSString *const kJSONKey = @"jsonKey"; // Note: NSObject's class is used as a marker for the expected/default class // when Discovery says it can be any type of object. @implementation GTLRuntimeCommon // Helper to generically convert JSON to an api object type. + (id)objectFromJSON:(id)json defaultClass:(Class)defaultClass surrogates:(NSDictionary *)surrogates isCacheable:(BOOL*)isCacheable { id result = nil; BOOL canBeCached = YES; // TODO(TVL): use defaultClass to validate things like expectedClass is // done in jsonFromAPIObject:expectedClass:isCacheable:? if ([json isKindOfClass:[NSDictionary class]]) { // If no default, or the default was any object, then default to base // object here (and hope there is a kind to get the right thing). if ((defaultClass == Nil) || [defaultClass isEqual:[NSObject class]]) { defaultClass = [GTLObject class]; } result = [GTLObject objectForJSON:json defaultClass:defaultClass surrogates:surrogates batchClassMap:nil]; } else if ([json isKindOfClass:[NSArray class]]) { NSArray *jsonArray = json; // make an object for each JSON dictionary in the array NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:[jsonArray count]]; for (id jsonItem in jsonArray) { id item = [self objectFromJSON:jsonItem defaultClass:defaultClass surrogates:surrogates isCacheable:NULL]; [resultArray addObject:item]; } result = resultArray; } else if ([json isKindOfClass:[NSString class]]) { // DateTimes live in JSON as strings, so convert if ([defaultClass isEqual:[GTLDateTime class]]) { result = [GTLDateTime dateTimeWithRFC3339String:json]; } else { result = json; canBeCached = NO; } } else if ([json isKindOfClass:[NSNumber class]] || [json isKindOfClass:[NSNull class]]) { result = json; canBeCached = NO; } else { GTL_DEBUG_LOG(@"GTLRuntimeCommon: unsupported class '%s' in objectFromJSON", class_getName([json class])); } if (isCacheable) { *isCacheable = canBeCached; } return result; } // Helper to generically convert an api object type to JSON. // |expectedClass| is the type that was expected for |obj|. + (id)jsonFromAPIObject:(id)obj expectedClass:(Class)expectedClass isCacheable:(BOOL*)isCacheable { id result = nil; BOOL canBeCached = YES; BOOL checkExpected = (expectedClass != Nil); if ([obj isKindOfClass:[NSString class]]) { result = [[obj copy] autorelease]; canBeCached = NO; } else if ([obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:[NSNull class]]) { result = obj; canBeCached = NO; } else if ([obj isKindOfClass:[GTLObject class]]) { result = [obj JSON]; } else if ([obj isKindOfClass:[NSArray class]]) { checkExpected = NO; NSArray *array = obj; // get the JSON for each thing in the array NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:[array count]]; for (id item in array) { id itemJSON = [self jsonFromAPIObject:item expectedClass:expectedClass isCacheable:NULL]; [resultArray addObject:itemJSON]; } result = resultArray; } else if ([obj isKindOfClass:[GTLDateTime class]]) { // DateTimes live in JSON as strings, so convert. GTLDateTime *dateTime = obj; result = [dateTime stringValue]; } else { checkExpected = NO; GTL_DEBUG_LOG(@"GTLRuntimeCommon: unsupported class '%s' in jsonFromAPIObject", class_getName([obj class])); } if (checkExpected) { // If the default was any object, then clear it to skip validation checks. if ([expectedClass isEqual:[NSObject class]] || [obj isKindOfClass:[NSNull class]]) { expectedClass = nil; } if (expectedClass && ![obj isKindOfClass:expectedClass]) { GTL_DEBUG_LOG(@"GTLRuntimeCommon: jsonFromAPIObject expected class '%s' instead got '%s'", class_getName(expectedClass), class_getName([obj class])); } } if (isCacheable) { *isCacheable = canBeCached; } return result; } #pragma mark JSON/Object Utilities static NSMutableDictionary *gDispatchCache = nil; static CFStringRef SelectorKeyCopyDescriptionCallBack(const void *key) { // Make a CFString from the key NSString *name = NSStringFromSelector((SEL) key); CFStringRef str = CFStringCreateCopy(kCFAllocatorDefault, (CFStringRef) name); return str; } // Save the dispatch details for the specified class and selector + (void)setStoredDispatchForClass:(Class)dispatchClass selector:(SEL)sel returnClass:(Class)returnClass containedClass:(Class)containedClass jsonKey:(NSString *)jsonKey { // cache structure: // class -> // selector -> // returnClass // containedClass // jsonKey @synchronized([GTLRuntimeCommon class]) { if (gDispatchCache == nil) { gDispatchCache = [GTLUtilities newStaticDictionary]; } CFMutableDictionaryRef classDict = (CFMutableDictionaryRef) [gDispatchCache objectForKey:dispatchClass]; if (classDict == nil) { // We create a CFDictionary since the keys are raw selectors rather than // NSStrings const CFDictionaryKeyCallBacks keyCallBacks = { .version = 0, .retain = NULL, .release = NULL, .copyDescription = SelectorKeyCopyDescriptionCallBack, .equal = NULL, // defaults to pointer comparison .hash = NULL // defaults to the pointer value }; const CFIndex capacity = 0; // no limit classDict = CFDictionaryCreateMutable(kCFAllocatorDefault, capacity, &keyCallBacks, &kCFTypeDictionaryValueCallBacks); [gDispatchCache setObject:(id)classDict forKey:(id)dispatchClass]; CFRelease(classDict); } NSDictionary *selDict = (NSDictionary *)CFDictionaryGetValue(classDict, sel); if (selDict == nil) { selDict = [NSDictionary dictionaryWithObjectsAndKeys: jsonKey, kJSONKey, returnClass, kReturnClassKey, // can be nil (primitive types) containedClass, kContainedClassKey, // may be nil nil]; CFDictionarySetValue(classDict, sel, selDict); } else { // we already have a dictionary for this selector on this class, which is // surprising GTL_DEBUG_LOG(@"Storing duplicate dispatch for %@ selector %@", dispatchClass, NSStringFromSelector(sel)); } } } + (BOOL)getStoredDispatchForClass:(Class)dispatchClass selector:(SEL)sel returnClass:(Class *)outReturnClass containedClass:(Class *)outContainedClass jsonKey:(NSString **)outJsonKey { @synchronized([GTLRuntimeCommon class]) { // walk from this class up the hierarchy to the ancestor class Class topClass = class_getSuperclass([dispatchClass ancestorClass]); for (Class currClass = dispatchClass; currClass != topClass; currClass = class_getSuperclass(currClass)) { CFMutableDictionaryRef classDict = (CFMutableDictionaryRef) [gDispatchCache objectForKey:currClass]; if (classDict) { NSMutableDictionary *selDict = (NSMutableDictionary *) CFDictionaryGetValue(classDict, sel); if (selDict) { if (outReturnClass) { *outReturnClass = [selDict objectForKey:kReturnClassKey]; } if (outContainedClass) { *outContainedClass = [selDict objectForKey:kContainedClassKey]; } if (outJsonKey) { *outJsonKey = [selDict objectForKey:kJSONKey]; } return YES; } } } } GTL_DEBUG_LOG(@"Failed to find stored dispatch info for %@ %s", dispatchClass, sel_getName(sel)); return NO; } #pragma mark IMPs - getters and setters for specific object types #if !__LP64__ // NSInteger on 32bit static NSInteger DynamicInteger32Getter(id self, SEL sel) { // get an NSInteger (NSNumber) from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [self JSONValueForKey:jsonKey]; num = GTL_EnsureNSNumber(num); NSInteger result = [num integerValue]; return result; } return 0; } static void DynamicInteger32Setter(id self, SEL sel, NSInteger val) { // save an NSInteger (NSNumber) into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [NSNumber numberWithInteger:val]; [self setJSONValue:num forKey:jsonKey]; } } // NSUInteger on 32bit static NSUInteger DynamicUInteger32Getter(id self, SEL sel) { // get an NSUInteger (NSNumber) from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [self JSONValueForKey:jsonKey]; num = GTL_EnsureNSNumber(num); NSUInteger result = [num unsignedIntegerValue]; return result; } return 0; } static void DynamicUInteger32Setter(id self, SEL sel, NSUInteger val) { // save an NSUInteger (NSNumber) into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [NSNumber numberWithUnsignedInteger:val]; [self setJSONValue:num forKey:jsonKey]; } } #endif // !__LP64__ // NSInteger on 64bit, long long on 32bit and 64bit static long long DynamicLongLongGetter(id self, SEL sel) { // get a long long (NSNumber) from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [self JSONValueForKey:jsonKey]; num = GTL_EnsureNSNumber(num); long long result = [num longLongValue]; return result; } return 0; } static void DynamicLongLongSetter(id self, SEL sel, long long val) { // save a long long (NSNumber) into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [NSNumber numberWithLongLong:val]; [self setJSONValue:num forKey:jsonKey]; } } // NSUInteger on 64bit, unsiged long long on 32bit and 64bit static unsigned long long DynamicULongLongGetter(id self, SEL sel) { // get an unsigned long long (NSNumber) from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [self JSONValueForKey:jsonKey]; num = GTL_EnsureNSNumber(num); unsigned long long result = [num unsignedLongLongValue]; return result; } return 0; } static void DynamicULongLongSetter(id self, SEL sel, unsigned long long val) { // save an unsigned long long (NSNumber) into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [NSNumber numberWithUnsignedLongLong:val]; [self setJSONValue:num forKey:jsonKey]; } } // float static float DynamicFloatGetter(id self, SEL sel) { // get a float (NSNumber) from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [self JSONValueForKey:jsonKey]; num = GTL_EnsureNSNumber(num); float result = [num floatValue]; return result; } return 0.0f; } static void DynamicFloatSetter(id self, SEL sel, float val) { // save a float (NSNumber) into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [NSNumber numberWithFloat:val]; [self setJSONValue:num forKey:jsonKey]; } } // double static double DynamicDoubleGetter(id self, SEL sel) { // get a double (NSNumber) from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [self JSONValueForKey:jsonKey]; num = GTL_EnsureNSNumber(num); double result = [num doubleValue]; return result; } return 0.0; } static void DynamicDoubleSetter(id self, SEL sel, double val) { // save a double (NSNumber) into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [NSNumber numberWithDouble:val]; [self setJSONValue:num forKey:jsonKey]; } } // BOOL static BOOL DynamicBooleanGetter(id self, SEL sel) { // get a BOOL (NSNumber) from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [self JSONValueForKey:jsonKey]; BOOL flag = [num boolValue]; return flag; } return NO; } static void DynamicBooleanSetter(id self, SEL sel, BOOL val) { // save a BOOL (NSNumber) into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [NSNumber numberWithBool:val]; [self setJSONValue:num forKey:jsonKey]; } } // NSString static NSString *DynamicStringGetter(id self, SEL sel) { // get an NSString from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSString *str = [self JSONValueForKey:jsonKey]; return str; } return nil; } static void DynamicStringSetter(id self, SEL sel, NSString *str) { // save an NSString into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSString *copiedStr = [str copy]; [self setJSONValue:copiedStr forKey:jsonKey]; [copiedStr release]; } } // GTLDateTime static GTLDateTime *DynamicDateTimeGetter(id self, SEL sel) { // get a GTLDateTime from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { // Return the cached object before creating on demand. GTLDateTime *cachedDateTime = [self cacheChildForKey:jsonKey]; if (cachedDateTime != nil) { return cachedDateTime; } NSString *str = [self JSONValueForKey:jsonKey]; id cacheValue, resultValue; if (![str isKindOfClass:[NSNull class]]) { GTLDateTime *dateTime = [GTLDateTime dateTimeWithRFC3339String:str]; cacheValue = dateTime; resultValue = dateTime; } else { cacheValue = nil; resultValue = [NSNull null]; } [self setCacheChild:cacheValue forKey:jsonKey]; return resultValue; } return nil; } static void DynamicDateTimeSetter(id self, SEL sel, GTLDateTime *dateTime) { // save an GTLDateTime into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { id cacheValue, jsonValue; if (![dateTime isKindOfClass:[NSNull class]]) { jsonValue = [dateTime stringValue]; cacheValue = dateTime; } else { jsonValue = [NSNull null]; cacheValue = nil; } [self setJSONValue:jsonValue forKey:jsonKey]; [self setCacheChild:cacheValue forKey:jsonKey]; } } // NSNumber static NSNumber *DynamicNumberGetter(id self, SEL sel) { // get an NSNumber from the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { NSNumber *num = [self JSONValueForKey:jsonKey]; num = GTL_EnsureNSNumber(num); return num; } return nil; } static void DynamicNumberSetter(id self, SEL sel, NSNumber *num) { // save an NSNumber into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { [self setJSONValue:num forKey:jsonKey]; } } // GTLObject static GTLObject *DynamicObjectGetter(id self, SEL sel) { // get a GTLObject from the JSON dictionary NSString *jsonKey = nil; Class returnClass = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:&returnClass containedClass:NULL jsonKey:&jsonKey]) { // Return the cached object before creating on demand. GTLObject *cachedObj = [self cacheChildForKey:jsonKey]; if (cachedObj != nil) { return cachedObj; } NSMutableDictionary *dict = [self JSONValueForKey:jsonKey]; if ([dict isKindOfClass:[NSMutableDictionary class]]) { // get the class of the object being returned, and instantiate it if (returnClass == Nil) { returnClass = [GTLObject class]; } NSDictionary *surrogates = self.surrogates; GTLObject *obj = [GTLObject objectForJSON:dict defaultClass:returnClass surrogates:surrogates batchClassMap:nil]; [self setCacheChild:obj forKey:jsonKey]; return obj; } else if ([dict isKindOfClass:[NSNull class]]) { [self setCacheChild:nil forKey:jsonKey]; return (id) [NSNull null]; } else if (dict != nil) { // unexpected; probably got a string -- let the caller figure it out GTL_DEBUG_LOG(@"GTLObject: unexpected JSON: %@.%@ should be a dictionary, actually is a %@:\n%@", NSStringFromClass(selfClass), NSStringFromSelector(sel), NSStringFromClass([dict class]), dict); return (GTLObject *)dict; } } return nil; } static void DynamicObjectSetter(id self, SEL sel, GTLObject *obj) { // save a GTLObject into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { id cacheValue, jsonValue; if (![obj isKindOfClass:[NSNull class]]) { NSMutableDictionary *dict = [obj JSON]; if (dict == nil && obj != nil) { // adding an empty object; it should have a JSON dictionary so it can // hold future assignments obj.JSON = [NSMutableDictionary dictionary]; jsonValue = obj.JSON; } else { jsonValue = dict; } cacheValue = obj; } else { jsonValue = [NSNull null]; cacheValue = nil; } [self setJSONValue:jsonValue forKey:jsonKey]; [self setCacheChild:cacheValue forKey:jsonKey]; } } // get an NSArray of GTLObjects, NSStrings, or NSNumbers from the // JSON dictionary for this object static NSMutableArray *DynamicArrayGetter(id self, SEL sel) { NSString *jsonKey = nil; Class containedClass = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:&containedClass jsonKey:&jsonKey]) { // Return the cached array before creating on demand. NSMutableArray *cachedArray = [self cacheChildForKey:jsonKey]; if (cachedArray != nil) { return cachedArray; } NSMutableArray *result = nil; NSArray *array = [self JSONValueForKey:jsonKey]; if (array != nil) { if ([array isKindOfClass:[NSArray class]]) { NSDictionary *surrogates = self.surrogates; result = [GTLRuntimeCommon objectFromJSON:array defaultClass:containedClass surrogates:surrogates isCacheable:NULL]; } else { #if DEBUG if (![array isKindOfClass:[NSNull class]]) { GTL_DEBUG_LOG(@"GTLObject: unexpected JSON: %@.%@ should be an array, actually is a %@:\n%@", NSStringFromClass(selfClass), NSStringFromSelector(sel), NSStringFromClass([array class]), array); } #endif result = (NSMutableArray *)array; } } [self setCacheChild:result forKey:jsonKey]; return result; } return nil; } static void DynamicArraySetter(id self, SEL sel, NSMutableArray *array) { // save an array of GTLObjects objects into the JSON dictionary NSString *jsonKey = nil; Class selfClass = [self class]; Class containedClass = nil; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:&containedClass jsonKey:&jsonKey]) { id json = [GTLRuntimeCommon jsonFromAPIObject:array expectedClass:containedClass isCacheable:NULL]; [self setJSONValue:json forKey:jsonKey]; [self setCacheChild:array forKey:jsonKey]; } } // type 'id' static id DynamicNSObjectGetter(id self, SEL sel) { NSString *jsonKey = nil; Class returnClass = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:&returnClass containedClass:NULL jsonKey:&jsonKey]) { // Return the cached object before creating on demand. id cachedObj = [self cacheChildForKey:jsonKey]; if (cachedObj != nil) { return cachedObj; } id jsonObj = [self JSONValueForKey:jsonKey]; if (jsonObj != nil) { BOOL shouldCache = NO; NSDictionary *surrogates = self.surrogates; id result = [GTLRuntimeCommon objectFromJSON:jsonObj defaultClass:nil surrogates:surrogates isCacheable:&shouldCache]; [self setCacheChild:(shouldCache ? result : nil) forKey:jsonKey]; return result; } } return nil; } static void DynamicNSObjectSetter(id self, SEL sel, id obj) { NSString *jsonKey = nil; Class selfClass = [self class]; if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass selector:sel returnClass:NULL containedClass:NULL jsonKey:&jsonKey]) { BOOL shouldCache = NO; id json = [GTLRuntimeCommon jsonFromAPIObject:obj expectedClass:Nil isCacheable:&shouldCache]; [self setJSONValue:json forKey:jsonKey]; [self setCacheChild:(shouldCache ? obj : nil) forKey:jsonKey]; } } #pragma mark Runtime lookup support static objc_property_t PropertyForSel(Class startClass, SEL sel, BOOL isSetter, Class *outFoundClass) { const char *baseName = sel_getName(sel); size_t baseNameLen = strlen(baseName); if (isSetter) { baseName += 3; // skip "set" baseNameLen -= 4; // subtract "set" and the final colon } // walk from this class up the hierarchy to the ancestor class Class topClass = class_getSuperclass([startClass ancestorClass]); for (Class currClass = startClass; currClass != topClass; currClass = class_getSuperclass(currClass)) { // step through this class's properties objc_property_t foundProp = NULL; objc_property_t *properties = class_copyPropertyList(currClass, NULL); if (properties) { for (objc_property_t *prop = properties; *prop != NULL; ++prop) { const char *propName = property_getName(*prop); size_t propNameLen = strlen(propName); // search for an exact-name match (a getter), but case-insensitive on the // first character (in case baseName comes from a setter) if (baseNameLen == propNameLen && strncasecmp(baseName, propName, 1) == 0 && (baseNameLen <= 1 || strncmp(baseName + 1, propName + 1, baseNameLen - 1) == 0)) { // return the actual property name foundProp = *prop; // if requested, return the class containing the property if (outFoundClass) *outFoundClass = currClass; break; } } free(properties); } if (foundProp) return foundProp; } // not found; this occasionally happens when the system looks for a method // like "getFoo" or "descriptionWithLocale:indent:" return NULL; } typedef struct { const char *attributePrefix; const char *setterEncoding; IMP setterFunction; const char *getterEncoding; IMP getterFunction; // These are the "fixed" return classes, but some properties will require // looking up the return class instead (because it is a subclass of // GTLObject). const char *returnClassName; Class returnClass; BOOL extractReturnClass; } GTLDynamicImpInfo; static const GTLDynamicImpInfo *DynamicImpInfoForProperty(objc_property_t prop, Class *outReturnClass) { if (outReturnClass) *outReturnClass = nil; // dynamic method resolution: // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html // // property runtimes: // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html // Get and parse the property attributes, which look something like // T@"NSString",&,D,P // Ti,D -- NSInteger on 32bit // Tq,D -- NSInteger on 64bit, long long on 32bit & 64bit // Tc,D -- BOOL comes as char // T@"NSString",D // T@"GTLLink",D // T@"NSArray",D static GTLDynamicImpInfo kImplInfo[] = { #if !__LP64__ { // NSInteger on 32bit "Ti", "v@:i", (IMP)DynamicInteger32Setter, "i@:", (IMP)DynamicInteger32Getter, nil, nil, NO }, { // NSUInteger on 32bit "TI", "v@:I", (IMP)DynamicUInteger32Setter, "I@:", (IMP)DynamicUInteger32Getter, nil, nil, NO }, #endif { // NSInteger on 64bit, long long on 32bit and 64bit. "Tq", "v@:q", (IMP)DynamicLongLongSetter, "q@:", (IMP)DynamicLongLongGetter, nil, nil, NO }, { // NSUInteger on 64bit, long long on 32bit and 64bit. "TQ", "v@:Q", (IMP)DynamicULongLongSetter, "Q@:", (IMP)DynamicULongLongGetter, nil, nil, NO }, { // float "Tf", "v@:f", (IMP)DynamicFloatSetter, "f@:", (IMP)DynamicFloatGetter, nil, nil, NO }, { // double "Td", "v@:d", (IMP)DynamicDoubleSetter, "d@:", (IMP)DynamicDoubleGetter, nil, nil, NO }, { // BOOL "Tc", "v@:c", (IMP)DynamicBooleanSetter, "c@:", (IMP)DynamicBooleanGetter, nil, nil, NO }, { // NSString "T@\"NSString\"", "v@:@", (IMP)DynamicStringSetter, "@@:", (IMP)DynamicStringGetter, "NSString", nil, NO }, { // NSNumber "T@\"NSNumber\"", "v@:@", (IMP)DynamicNumberSetter, "@@:", (IMP)DynamicNumberGetter, "NSNumber", nil, NO }, { // GTLDateTime #if !defined(GTL_TARGET_NAMESPACE) "T@\"GTLDateTime\"", "v@:@", (IMP)DynamicDateTimeSetter, "@@:", (IMP)DynamicDateTimeGetter, "GTLDateTime", nil, NO #else "T@\"" GTL_TARGET_NAMESPACE_STRING "_" "GTLDateTime\"", "v@:@", (IMP)DynamicDateTimeSetter, "@@:", (IMP)DynamicDateTimeGetter, GTL_TARGET_NAMESPACE_STRING "_" "GTLDateTime", nil, NO #endif }, { // NSArray with type "T@\"NSArray\"", "v@:@", (IMP)DynamicArraySetter, "@@:", (IMP)DynamicArrayGetter, "NSArray", nil, NO }, { // id (any of the objects above) "T@,", "v@:@", (IMP)DynamicNSObjectSetter, "@@:", (IMP)DynamicNSObjectGetter, "NSObject", nil, NO }, { // GTLObject - Last, cause it's a special case and prefix is general "T@\"", "v@:@", (IMP)DynamicObjectSetter, "@@:", (IMP)DynamicObjectGetter, nil, nil, YES }, }; static BOOL hasLookedUpClasses = NO; if (!hasLookedUpClasses) { // Unfortunately, you can't put [NSString class] into the static structure, // so this lookup has to be done at runtime. hasLookedUpClasses = YES; for (uint32_t idx = 0; idx < sizeof(kImplInfo)/sizeof(kImplInfo[0]); ++idx) { if (kImplInfo[idx].returnClassName) { kImplInfo[idx].returnClass = objc_getClass(kImplInfo[idx].returnClassName); NSCAssert1(kImplInfo[idx].returnClass != nil, @"GTLRuntimeCommon: class lookup failed: %s", kImplInfo[idx].returnClassName); } } } const char *attr = property_getAttributes(prop); const char *dynamicMarker = strstr(attr, ",D"); if (!dynamicMarker || (dynamicMarker[2] != 0 && dynamicMarker[2] != ',' )) { GTL_DEBUG_LOG(@"GTLRuntimeCommon: property %s isn't dynamic, attributes %s", property_getName(prop), attr ? attr : "(nil)"); return NULL; } const GTLDynamicImpInfo *result = NULL; // Cycle over the list for (uint32_t idx = 0; idx < sizeof(kImplInfo)/sizeof(kImplInfo[0]); ++idx) { const char *attributePrefix = kImplInfo[idx].attributePrefix; if (strncmp(attr, attributePrefix, strlen(attributePrefix)) == 0) { result = &kImplInfo[idx]; if (outReturnClass) *outReturnClass = result->returnClass; break; } } if (result == NULL) { GTL_DEBUG_LOG(@"GTLRuntimeCommon: unexpected attributes %s for property %s", attr ? attr : "(nil)", property_getName(prop)); return NULL; } if (result->extractReturnClass && outReturnClass) { // add a null at the next quotation mark char *attrCopy = strdup(attr); char *classNameStart = attrCopy + 3; char *classNameEnd = strstr(classNameStart, "\""); if (classNameEnd) { *classNameEnd = '\0'; // Lookup the return class *outReturnClass = objc_getClass(classNameStart); if (*outReturnClass == nil) { GTL_DEBUG_LOG(@"GTLRuntimeCommon: did not find class with name \"%s\" " "for property \"%s\" with attributes \"%s\"", classNameStart, property_getName(prop), attr); } } else { GTL_DEBUG_LOG(@"GTLRuntimeCommon: Failed to find end of class name for " "property \"%s\" with attributes \"%s\"", property_getName(prop), attr); } free(attrCopy); } return result; } #pragma mark Runtime - wiring point + (BOOL)resolveInstanceMethod:(SEL)sel onClass:(Class)onClass { // dynamic method resolution: // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html // // property runtimes: // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html const char *selName = sel_getName(sel); size_t selNameLen = strlen(selName); char lastChar = selName[selNameLen - 1]; BOOL isSetter = (lastChar == ':'); // look for a declared property matching this selector name exactly Class foundClass = nil; objc_property_t prop = PropertyForSel(onClass, sel, isSetter, &foundClass); if (prop != NULL && foundClass != nil) { Class returnClass = nil; const GTLDynamicImpInfo *implInfo = DynamicImpInfoForProperty(prop, &returnClass); if (implInfo == NULL) { GTL_DEBUG_LOG(@"GTLRuntimeCommon: unexpected return type class %s for " "property \"%s\" of class \"%s\"", returnClass ? class_getName(returnClass) : "", property_getName(prop), class_getName(onClass)); } if (implInfo != NULL) { IMP imp = ( isSetter ? implInfo->setterFunction : implInfo->getterFunction ); const char *encoding = ( isSetter ? implInfo->setterEncoding : implInfo->getterEncoding ); class_addMethod(foundClass, sel, imp, encoding); const char *propName = property_getName(prop); NSString *propStr = [NSString stringWithUTF8String:propName]; // replace the property name with the proper JSON key if it's // special-cased with a map in the found class; otherwise, the property // name is the JSON key NSDictionary *keyMap = [[foundClass ancestorClass] propertyToJSONKeyMapForClass:foundClass]; NSString *jsonKey = [keyMap objectForKey:propStr]; if (jsonKey == nil) { jsonKey = propStr; } Class containedClass = nil; // For arrays we need to look up what the contained class is. if (imp == (IMP)DynamicArraySetter || imp == (IMP)DynamicArrayGetter) { NSDictionary *classMap = [[foundClass ancestorClass] arrayPropertyToClassMapForClass:foundClass]; containedClass = [classMap objectForKey:jsonKey]; if (containedClass == Nil) { GTL_DEBUG_LOG(@"GTLRuntimeCommon: expected array item class for " "property \"%s\" of class \"%s\"", property_getName(prop), class_getName(foundClass)); } } // save the dispatch info to the cache [GTLRuntimeCommon setStoredDispatchForClass:foundClass selector:sel returnClass:returnClass containedClass:containedClass jsonKey:jsonKey]; return YES; } } return NO; } @end