6c23134e47
[UPDATED] Google+ SDK to 1.1.0.
1142 lines
41 KiB
Objective-C
1142 lines
41 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.
|
|
*/
|
|
|
|
//
|
|
// GTLRuntimeCommon.m
|
|
//
|
|
|
|
#include <objc/runtime.h>
|
|
|
|
#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];
|
|
if (result == nil) {
|
|
// adding an empty object; it should have a JSON dictionary so it can
|
|
// hold future assignments
|
|
[obj setJSON:[NSMutableDictionary dictionary]];
|
|
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<GTLRuntimeCommon>)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<NSCopying>)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<GTLRuntimeCommon>)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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> self, SEL sel) {
|
|
// get an NSString from the JSON dictionary
|
|
NSString *jsonKey = nil;
|
|
Class<GTLRuntimeCommon> 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<GTLRuntimeCommon> self, SEL sel,
|
|
NSString *str) {
|
|
// save an NSString into the JSON dictionary
|
|
NSString *jsonKey = nil;
|
|
Class<GTLRuntimeCommon> 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<GTLRuntimeCommon> self, SEL sel) {
|
|
// get a GTLDateTime from the JSON dictionary
|
|
NSString *jsonKey = nil;
|
|
Class<GTLRuntimeCommon> 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<GTLRuntimeCommon> self, SEL sel,
|
|
GTLDateTime *dateTime) {
|
|
// save an GTLDateTime into the JSON dictionary
|
|
NSString *jsonKey = nil;
|
|
Class<GTLRuntimeCommon> 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<GTLRuntimeCommon> self, SEL sel) {
|
|
// get an NSNumber from the JSON dictionary
|
|
NSString *jsonKey = nil;
|
|
Class<GTLRuntimeCommon> 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<GTLRuntimeCommon> self, SEL sel,
|
|
NSNumber *num) {
|
|
// save an NSNumber into the JSON dictionary
|
|
NSString *jsonKey = nil;
|
|
Class<GTLRuntimeCommon> 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<GTLRuntimeCommon> self, SEL sel) {
|
|
// get a GTLObject from the JSON dictionary
|
|
NSString *jsonKey = nil;
|
|
Class returnClass = nil;
|
|
Class<GTLRuntimeCommon> 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<GTLRuntimeCommon> self, SEL sel,
|
|
GTLObject *obj) {
|
|
// save a GTLObject into the JSON dictionary
|
|
NSString *jsonKey = nil;
|
|
Class<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> 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<GTLRuntimeCommon> startClass,
|
|
SEL sel, BOOL isSetter,
|
|
Class<GTLRuntimeCommon> *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<GTLRuntimeCommon> 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<GTLRuntimeCommon>)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<GTLRuntimeCommon> 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) : "<nil>",
|
|
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
|