GBA003/Pods/GoogleAPIClientForREST/Source/Objects/GTLRRuntimeCommon.m
2024-05-30 10:22:15 +08:00

1061 lines
35 KiB
Objective-C

/* 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.
*/
#if !__has_feature(objc_arc)
#error "This file needs to be compiled with ARC enabled."
#endif
#include <objc/runtime.h>
#include <TargetConditionals.h>
#import "GTLRRuntimeCommon.h"
#import "GTLRDateTime.h"
#import "GTLRDuration.h"
#import "GTLRObject.h"
#import "GTLRUtilities.h"
// 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 GTLRRuntimeCommon
// Helper to generically convert JSON to an api object type.
+ (id)objectFromJSON:(id)json
defaultClass:(Class)defaultClass
objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver
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 = [GTLRObject class];
}
result = [GTLRObject objectForJSON:json
defaultClass:defaultClass
objectClassResolver:objectClassResolver];
} 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
objectClassResolver:objectClassResolver
isCacheable:NULL];
[resultArray addObject:item];
}
result = resultArray;
} else if ([json isKindOfClass:[NSString class]]) {
// DateTimes and Durations live in JSON as strings, so convert.
if ([defaultClass isEqual:[GTLRDateTime class]]) {
result = [GTLRDateTime dateTimeWithRFC3339String:json];
} else if ([defaultClass isEqual:[GTLRDuration class]]) {
result = [GTLRDuration durationWithJSONString:json];
} else if ([defaultClass isEqual:[NSNumber class]]) {
result = GTLR_EnsureNSNumber(json);
canBeCached = NO;
} else {
result = json;
canBeCached = NO;
}
} else if ([json isKindOfClass:[NSNumber class]] ||
[json isKindOfClass:[NSNull class]]) {
result = json;
canBeCached = NO;
} else {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: 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];
canBeCached = NO;
} else if ([obj isKindOfClass:[NSNumber class]] ||
[obj isKindOfClass:[NSNull class]]) {
result = obj;
canBeCached = NO;
} else if ([obj isKindOfClass:[GTLRObject class]]) {
result = [(GTLRObject *)obj JSON];
if (result == nil) {
// adding an empty object; it should have a JSON dictionary so it can
// hold future assignments
[(GTLRObject *)obj setJSON:[NSMutableDictionary dictionary]];
result = [(GTLRObject *)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:[GTLRDateTime class]]) {
// DateTimes live in JSON as strings, so convert.
GTLRDateTime *dateTime = obj;
result = dateTime.RFC3339String;
} else if ([obj isKindOfClass:[GTLRDuration class]]) {
// Durations live in JSON as strings, so convert.
GTLRDuration *duration = obj;
result = duration.jsonString;
} else {
checkExpected = NO;
if (obj) {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: 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]) {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: jsonFromAPIObject expected class '%s' instead got '%s'",
class_getName(expectedClass), class_getName([obj class]));
}
}
if (isCacheable) {
*isCacheable = canBeCached;
}
return result;
}
+ (NSDictionary *)mergedClassDictionaryForSelector:(SEL)selector
startClass:(Class)startClass
ancestorClass:(Class)ancestorClass
cache:(NSMutableDictionary *)cache {
NSDictionary *result;
@synchronized(cache) {
result = [cache objectForKey:startClass];
if (result == nil) {
// Collect the class's dictionary.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
NSDictionary *classDict = [startClass performSelector:selector];
#pragma clang diagnostic pop
// Collect the parent class's merged dictionary.
NSDictionary *parentClassMergedDict;
if ([startClass isEqual:ancestorClass]) {
parentClassMergedDict = nil;
} else {
Class parentClass = class_getSuperclass(startClass);
parentClassMergedDict =
[self mergedClassDictionaryForSelector:selector
startClass:parentClass
ancestorClass:ancestorClass
cache:cache];
}
// Merge this class's into the parent's so things properly override.
NSMutableDictionary *mergeDict;
if (parentClassMergedDict != nil) {
mergeDict =
[NSMutableDictionary dictionaryWithDictionary:parentClassMergedDict];
} else {
mergeDict = [NSMutableDictionary dictionary];
}
if (classDict != nil) {
[mergeDict addEntriesFromDictionary:classDict];
}
// Make an immutable version.
result = [NSDictionary dictionaryWithDictionary:mergeDict];
// Save it.
[cache setObject:result forKey:(id<NSCopying>)startClass];
}
}
return result;
}
#pragma mark Runtime lookup support
static objc_property_t PropertyForSel(Class<GTLRRuntimeCommon> startClass,
SEL sel, BOOL isSetter,
Class<GTLRRuntimeCommon> *outFoundClass) {
const char *selName = sel_getName(sel);
const char *baseName = selName;
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<GTLRRuntimeCommon> 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 *propAttrs = property_getAttributes(*prop);
const char *dynamicMarker = strstr(propAttrs, ",D");
if (!dynamicMarker ||
(dynamicMarker[2] != 0 && dynamicMarker[2] != ',' )) {
// It isn't dynamic, skip it.
continue;
}
if (!isSetter) {
// See if this property has an explicit getter=. (the attributes always start with a T,
// so we can check for the leading ','.
const char *getterMarker = strstr(propAttrs, ",G");
if (getterMarker) {
const char *getterStart = getterMarker + 2;
const char *getterEnd = getterStart;
while ((*getterEnd != 0) && (*getterEnd != ',')) {
++getterEnd;
}
size_t getterLen = (size_t)(getterEnd - getterStart);
if ((strncmp(selName, getterStart, getterLen) == 0)
&& (selName[getterLen] == 0)) {
// return the actual property
foundProp = *prop;
// if requested, return the class containing the property
if (outFoundClass) *outFoundClass = currClass;
break;
}
} // if (getterMarker)
} // if (!isSetter)
// Search for an exact-name match (a getter), but case-insensitive on the
// first character (in case baseName comes from a setter)
const char *propName = property_getName(*prop);
size_t propNameLen = strlen(propName);
if (baseNameLen == propNameLen
&& strncasecmp(baseName, propName, 1) == 0
&& (baseNameLen <= 1
|| strncmp(baseName + 1, propName + 1, baseNameLen - 1) == 0)) {
// return the actual property
foundProp = *prop;
// if requested, return the class containing the property
if (outFoundClass) *outFoundClass = currClass;
break;
}
} // for (prop in properties)
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 NS_ENUM(NSUInteger, GTLRPropertyType) {
#if !defined(__LP64__) || !__LP64__
// These two only needed in 32bit builds since NSInteger in 64bit ends up in the LongLong paths.
GTLRPropertyTypeInt32 = 1,
GTLRPropertyTypeUInt32,
#endif
GTLRPropertyTypeLongLong = 3,
GTLRPropertyTypeULongLong,
GTLRPropertyTypeFloat,
GTLRPropertyTypeDouble,
GTLRPropertyTypeBool,
GTLRPropertyTypeNSString,
GTLRPropertyTypeNSNumber,
GTLRPropertyTypeGTLRDateTime,
GTLRPropertyTypeGTLRDuration,
GTLRPropertyTypeNSArray,
GTLRPropertyTypeNSObject,
GTLRPropertyTypeGTLRObject,
};
typedef struct {
const char *attributePrefix;
GTLRPropertyType propertyType;
const char *setterEncoding;
const char *getterEncoding;
// These are the "fixed" return classes, but some properties will require
// looking up the return class instead (because it is a subclass of
// GTLRObject).
const char *returnClassName;
Class returnClass;
BOOL extractReturnClass;
} GTLRDynamicImpInfo;
static const GTLRDynamicImpInfo *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
// TB,D -- BOOL comes as bool on 64bit iOS
// Tc,D -- BOOL comes as char otherwise
// T@"NSString",D
// T@"GTLRLink",D
// T@"NSArray",D
static GTLRDynamicImpInfo kImplInfo[] = {
#if !defined(__LP64__) || !__LP64__
{ // NSInteger on 32bit
"Ti",
GTLRPropertyTypeInt32,
"v@:i",
"i@:",
nil, nil,
NO
},
{ // NSUInteger on 32bit
"TI",
GTLRPropertyTypeUInt32,
"v@:I",
"I@:",
nil, nil,
NO
},
#endif
{ // NSInteger on 64bit, long long on 32bit and 64bit.
"Tq",
GTLRPropertyTypeLongLong,
"v@:q",
"q@:",
nil, nil,
NO
},
{ // NSUInteger on 64bit, long long on 32bit and 64bit.
"TQ",
GTLRPropertyTypeULongLong,
"v@:Q",
"Q@:",
nil, nil,
NO
},
{ // float
"Tf",
GTLRPropertyTypeFloat,
"v@:f",
"f@:",
nil, nil,
NO
},
{ // double
"Td",
GTLRPropertyTypeDouble,
"v@:d",
"d@:",
nil, nil,
NO
},
#if defined(OBJC_BOOL_IS_BOOL) && OBJC_BOOL_IS_BOOL
{ // BOOL as bool
"TB",
GTLRPropertyTypeBool,
"v@:B",
"B@:",
nil, nil,
NO
},
#elif defined(OBJC_BOOL_IS_CHAR) && OBJC_BOOL_IS_CHAR
{ // BOOL as char
"Tc",
GTLRPropertyTypeBool,
"v@:c",
"c@:",
nil, nil,
NO
},
#else
#error unknown definition for ObjC BOOL type
#endif
{ // NSString
"T@\"NSString\"",
GTLRPropertyTypeNSString,
"v@:@",
"@@:",
"NSString", nil,
NO
},
{ // NSNumber
"T@\"NSNumber\"",
GTLRPropertyTypeNSNumber,
"v@:@",
"@@:",
"NSNumber", nil,
NO
},
{ // GTLRDateTime
"T@\"" GTLR_CLASSNAME_CSTR(GTLRDateTime) "\"",
GTLRPropertyTypeGTLRDateTime,
"v@:@",
"@@:",
GTLR_CLASSNAME_CSTR(GTLRDateTime), nil,
NO
},
{ // GTLRDuration
"T@\"" GTLR_CLASSNAME_CSTR(GTLRDuration) "\"",
GTLRPropertyTypeGTLRDuration,
"v@:@",
"@@:",
GTLR_CLASSNAME_CSTR(GTLRDuration), nil,
NO
},
{ // NSArray with type
"T@\"NSArray\"",
GTLRPropertyTypeNSArray,
"v@:@",
"@@:",
"NSArray", nil,
NO
},
{ // id (any of the objects above)
"T@,",
GTLRPropertyTypeNSObject,
"v@:@",
"@@:",
"NSObject", nil,
NO
},
{ // GTLRObject - Last, cause it's a special case and prefix is general
"T@\"",
GTLRPropertyTypeGTLRObject,
"v@:@",
"@@:",
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,
@"GTLRRuntimeCommon: 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] != ',' )) {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: property %s isn't dynamic, attributes %s",
property_getName(prop), attr ? attr : "(nil)");
return NULL;
}
const GTLRDynamicImpInfo *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) {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: 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) {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: did not find class with name \"%s\" "
@"for property \"%s\" with attributes \"%s\"",
classNameStart, property_getName(prop), attr);
}
} else {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: Failed to find end of class name for "
@"property \"%s\" with attributes \"%s\"",
property_getName(prop), attr);
}
free(attrCopy);
}
return result;
}
// Helper to get the IMP for wiring up the getters.
// NOTE: Every argument passed in should be safe to capture in a block. Avoid
// passing something like selName instead of sel, because nothing says that
// pointer will be valid when it is finally used when the method IMP is invoked
// some time later.
static IMP GTLRRuntimeGetterIMP(SEL sel,
GTLRPropertyType propertyType,
NSString *jsonKey,
Class containedClass,
Class returnClass) {
// Only used in DEBUG logging.
#pragma unused(sel)
IMP resultIMP;
switch (propertyType) {
#if !defined(__LP64__) || !__LP64__
case GTLRPropertyTypeInt32: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSNumber *num = [obj JSONValueForKey:jsonKey];
num = GTLR_EnsureNSNumber(num);
NSInteger result = num.integerValue;
return result;
});
break;
}
case GTLRPropertyTypeUInt32: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSNumber *num = [obj JSONValueForKey:jsonKey];
num = GTLR_EnsureNSNumber(num);
NSUInteger result = num.unsignedIntegerValue;
return result;
});
break;
}
#endif // __LP64__
case GTLRPropertyTypeLongLong: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSNumber *num = [obj JSONValueForKey:jsonKey];
num = GTLR_EnsureNSNumber(num);
long long result = num.longLongValue;
return result;
});
break;
}
case GTLRPropertyTypeULongLong: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSNumber *num = [obj JSONValueForKey:jsonKey];
num = GTLR_EnsureNSNumber(num);
unsigned long long result = num.unsignedLongLongValue;
return result;
});
break;
}
case GTLRPropertyTypeFloat: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSNumber *num = [obj JSONValueForKey:jsonKey];
num = GTLR_EnsureNSNumber(num);
float result = num.floatValue;
return result;
});
break;
}
case GTLRPropertyTypeDouble: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSNumber *num = [obj JSONValueForKey:jsonKey];
num = GTLR_EnsureNSNumber(num);
double result = num.doubleValue;
return result;
});
break;
}
case GTLRPropertyTypeBool: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSNumber *num = [obj JSONValueForKey:jsonKey];
BOOL flag = num.boolValue;
return flag;
});
break;
}
case GTLRPropertyTypeNSString: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSString *str = [obj JSONValueForKey:jsonKey];
return str;
});
break;
}
case GTLRPropertyTypeGTLRDateTime: {
resultIMP = imp_implementationWithBlock(^GTLRDateTime *(GTLRObject<GTLRRuntimeCommon> *obj) {
// Return the cached object before creating on demand.
GTLRDateTime *cachedDateTime = [obj cacheChildForKey:jsonKey];
if (cachedDateTime != nil) {
return cachedDateTime;
}
NSString *str = [obj JSONValueForKey:jsonKey];
id cacheValue, resultValue;
if (![str isKindOfClass:[NSNull class]]) {
GTLRDateTime *dateTime = [GTLRDateTime dateTimeWithRFC3339String:str];
cacheValue = dateTime;
resultValue = dateTime;
} else {
cacheValue = nil;
resultValue = [NSNull null];
}
[obj setCacheChild:cacheValue forKey:jsonKey];
return resultValue;
});
break;
}
case GTLRPropertyTypeGTLRDuration: {
resultIMP = imp_implementationWithBlock(^GTLRDuration *(GTLRObject<GTLRRuntimeCommon> *obj) {
// Return the cached object before creating on demand.
GTLRDuration *cachedDuration = [obj cacheChildForKey:jsonKey];
if (cachedDuration != nil) {
return cachedDuration;
}
NSString *str = [obj JSONValueForKey:jsonKey];
id cacheValue, resultValue;
if (![str isKindOfClass:[NSNull class]]) {
GTLRDuration *duration = [GTLRDuration durationWithJSONString:str];
cacheValue = duration;
resultValue = duration;
} else {
cacheValue = nil;
resultValue = [NSNull null];
}
[obj setCacheChild:cacheValue forKey:jsonKey];
return resultValue;
});
break;
}
case GTLRPropertyTypeNSNumber: {
resultIMP = imp_implementationWithBlock(^(id obj) {
NSNumber *num = [obj JSONValueForKey:jsonKey];
num = GTLR_EnsureNSNumber(num);
return num;
});
break;
}
case GTLRPropertyTypeGTLRObject: {
// Default return class to GTLRObject if it wasn't found.
if (returnClass == Nil) {
returnClass = [GTLRObject class];
}
resultIMP = imp_implementationWithBlock(^GTLRObject *(GTLRObject<GTLRRuntimeCommon> *obj) {
// Return the cached object before creating on demand.
GTLRObject *cachedObj = [obj cacheChildForKey:jsonKey];
if (cachedObj != nil) {
return cachedObj;
}
NSMutableDictionary *dict = [obj JSONValueForKey:jsonKey];
if ([dict isKindOfClass:[NSMutableDictionary class]]) {
id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver];
GTLRObject *subObj = [GTLRObject objectForJSON:dict
defaultClass:returnClass
objectClassResolver:objectClassResolver];
[obj setCacheChild:subObj forKey:jsonKey];
return subObj;
} else if ([dict isKindOfClass:[NSNull class]]) {
[obj setCacheChild:nil forKey:jsonKey];
return (GTLRObject*)[NSNull null];
} else if (dict != nil) {
// unexpected; probably got a string -- let the caller figure it out
GTLR_DEBUG_LOG(@"GTLRObject: unexpected JSON: %@.%@ should be a dictionary, actually is a %@:\n%@",
NSStringFromClass([obj class]),
NSStringFromSelector(sel),
NSStringFromClass([dict class]), dict);
return (GTLRObject *)dict;
}
return nil;
});
break;
}
case GTLRPropertyTypeNSArray: {
resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj) {
// Return the cached array before creating on demand.
NSMutableArray *cachedArray = [obj cacheChildForKey:jsonKey];
if (cachedArray != nil) {
return cachedArray;
}
NSMutableArray *result = nil;
NSArray *array = [obj JSONValueForKey:jsonKey];
if (array != nil) {
if ([array isKindOfClass:[NSArray class]]) {
id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver];
result = [GTLRRuntimeCommon objectFromJSON:array
defaultClass:containedClass
objectClassResolver:objectClassResolver
isCacheable:NULL];
} else {
#if DEBUG
if (![array isKindOfClass:[NSNull class]]) {
GTLR_DEBUG_LOG(@"GTLRObject: unexpected JSON: %@.%@ should be an array, actually is a %@:\n%@",
NSStringFromClass([obj class]),
NSStringFromSelector(sel),
NSStringFromClass([array class]), array);
}
#endif
result = (NSMutableArray *)array;
}
}
[obj setCacheChild:result forKey:jsonKey];
return result;
});
break;
}
case GTLRPropertyTypeNSObject: {
resultIMP = imp_implementationWithBlock(^id(GTLRObject<GTLRRuntimeCommon> *obj) {
// Return the cached object before creating on demand.
id cachedObj = [obj cacheChildForKey:jsonKey];
if (cachedObj != nil) {
return cachedObj;
}
id jsonObj = [obj JSONValueForKey:jsonKey];
if (jsonObj != nil) {
BOOL shouldCache = NO;
id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver];
id result = [GTLRRuntimeCommon objectFromJSON:jsonObj
defaultClass:nil
objectClassResolver:objectClassResolver
isCacheable:&shouldCache];
[obj setCacheChild:(shouldCache ? result : nil)
forKey:jsonKey];
return result;
}
return nil;
});
break;
}
} // switch(propertyType)
return resultIMP;
}
// Helper to get the IMP for wiring up the setters.
// NOTE: Every argument passed in should be safe to capture in a block. Avoid
// passing something like selName instead of sel, because nothing says that
// pointer will be valid when it is finally used when the method IMP is invoked
// some time later.
static IMP GTLRRuntimeSetterIMP(SEL sel,
GTLRPropertyType propertyType,
NSString *jsonKey,
Class containedClass,
Class returnClass) {
#pragma unused(sel, returnClass)
IMP resultIMP;
switch (propertyType) {
#if !defined(__LP64__) || !__LP64__
case GTLRPropertyTypeInt32: {
resultIMP = imp_implementationWithBlock(^(id obj, NSInteger val) {
[obj setJSONValue:@(val) forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeUInt32: {
resultIMP = imp_implementationWithBlock(^(id obj, NSUInteger val) {
[obj setJSONValue:@(val) forKey:jsonKey];
});
break;
}
#endif // __LP64__
case GTLRPropertyTypeLongLong: {
resultIMP = imp_implementationWithBlock(^(id obj, long long val) {
[obj setJSONValue:@(val) forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeULongLong: {
resultIMP = imp_implementationWithBlock(^(id obj,
unsigned long long val) {
[obj setJSONValue:@(val) forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeFloat: {
resultIMP = imp_implementationWithBlock(^(id obj, float val) {
[obj setJSONValue:@(val) forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeDouble: {
resultIMP = imp_implementationWithBlock(^(id obj, double val) {
[obj setJSONValue:@(val) forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeBool: {
resultIMP = imp_implementationWithBlock(^(id obj, BOOL val) {
NSNumber *numValue = (NSNumber *)(val ? kCFBooleanTrue : kCFBooleanFalse);
[obj setJSONValue:numValue forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeNSString: {
resultIMP = imp_implementationWithBlock(^(id obj, NSString *val) {
NSString *copiedStr = [val copy];
[obj setJSONValue:copiedStr forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeGTLRDateTime: {
resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
GTLRDateTime *val) {
id cacheValue, jsonValue;
if (![val isKindOfClass:[NSNull class]]) {
jsonValue = val.RFC3339String;
cacheValue = val;
} else {
jsonValue = [NSNull null];
cacheValue = nil;
}
[obj setJSONValue:jsonValue forKey:jsonKey];
[obj setCacheChild:cacheValue forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeGTLRDuration: {
resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
GTLRDuration *val) {
id cacheValue, jsonValue;
if (![val isKindOfClass:[NSNull class]]) {
jsonValue = val.jsonString;
cacheValue = val;
} else {
jsonValue = [NSNull null];
cacheValue = nil;
}
[obj setJSONValue:jsonValue forKey:jsonKey];
[obj setCacheChild:cacheValue forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeNSNumber: {
resultIMP = imp_implementationWithBlock(^(id obj, NSNumber *val) {
[obj setJSONValue:val forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeGTLRObject: {
resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
GTLRObject *val) {
id cacheValue, jsonValue;
if (![val isKindOfClass:[NSNull class]]) {
NSMutableDictionary *dict = [val JSON];
if (dict == nil && val != nil) {
// adding an empty object; it should have a JSON dictionary so it
// can hold future assignments
val.JSON = [NSMutableDictionary dictionary];
jsonValue = val.JSON;
} else {
jsonValue = dict;
}
cacheValue = val;
} else {
jsonValue = [NSNull null];
cacheValue = nil;
}
[obj setJSONValue:jsonValue forKey:jsonKey];
[obj setCacheChild:cacheValue forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeNSArray: {
resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
NSMutableArray *val) {
id json = [GTLRRuntimeCommon jsonFromAPIObject:val
expectedClass:containedClass
isCacheable:NULL];
[obj setJSONValue:json forKey:jsonKey];
[obj setCacheChild:val forKey:jsonKey];
});
break;
}
case GTLRPropertyTypeNSObject: {
resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
id val) {
BOOL shouldCache = NO;
id json = [GTLRRuntimeCommon jsonFromAPIObject:val
expectedClass:Nil
isCacheable:&shouldCache];
[obj setJSONValue:json forKey:jsonKey];
[obj setCacheChild:(shouldCache ? val : nil)
forKey:jsonKey];
});
break;
}
} // switch(propertyType)
return resultIMP;
}
#pragma mark Runtime - wiring point
+ (BOOL)resolveInstanceMethod:(SEL)sel onClass:(Class<GTLRRuntimeCommon>)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<GTLRRuntimeCommon> foundClass = nil;
objc_property_t prop = PropertyForSel(onClass, sel, isSetter, &foundClass);
if (prop == NULL || foundClass == nil) {
return NO; // No luck, out of here.
}
Class returnClass = nil;
const GTLRDynamicImpInfo *implInfo = DynamicImpInfoForProperty(prop,
&returnClass);
if (implInfo == NULL) {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: unexpected return type class %s for "
@"property \"%s\" of class \"%s\"",
returnClass ? class_getName(returnClass) : "<nil>",
property_getName(prop),
class_getName(onClass));
return NO; // Failed to find our impl info, out of here.
}
const char *propName = property_getName(prop);
NSString *propStr = @(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
// NOTE: These caches that are built up could likely be dropped and do this
// lookup on demand from the class tree. Most are checked once when a method
// is first resolved, so eventually become wasted memory.
NSDictionary *keyMap =
[[foundClass ancestorClass] propertyToJSONKeyMapForClass:foundClass];
NSString *jsonKey = [keyMap objectForKey:propStr];
if (jsonKey == nil) {
jsonKey = propStr;
}
// For arrays we need to look up what the contained class is.
Class containedClass = nil;
if (implInfo->propertyType == GTLRPropertyTypeNSArray) {
NSDictionary *classMap =
[[foundClass ancestorClass] arrayPropertyToClassMapForClass:foundClass];
containedClass = [classMap objectForKey:jsonKey];
if (containedClass == Nil) {
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: expected array item class for "
@"property \"%s\" of class \"%s\"",
property_getName(prop), class_getName(foundClass));
}
}
// Wire in the method.
IMP imp;
const char *encoding;
if (isSetter) {
imp = GTLRRuntimeSetterIMP(sel, implInfo->propertyType,
jsonKey, containedClass, returnClass);
encoding = implInfo->setterEncoding;
} else {
imp = GTLRRuntimeGetterIMP(sel, implInfo->propertyType,
jsonKey, containedClass, returnClass);
encoding = implInfo->getterEncoding;
}
if (class_addMethod(foundClass, sel, imp, encoding)) {
return YES;
}
// Not much we can do if this fails, but leave a breadcumb in the log.
GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: Failed to wire %@ on %@ (encoding: %s).",
NSStringFromSelector(sel),
NSStringFromClass(foundClass),
encoding);
return NO;
}
@end