GBA001/Pods/GoogleAPIClientForREST/Source/Objects/GTLRRuntimeCommon.m
Riley Testut 6cca0f244f Replaces frameworks with static libraries
As of iOS 13.3.1, apps installed with free developer accounts that contain embedded frameworks fail to launch. To work around this, we now link all dependencies via Cocoapods as static libraries.
2020-02-03 19:28:23 -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