GTLRRuntimeCommon.m 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  1. /* Copyright (c) 2011 Google Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. #include <objc/runtime.h>
  16. #include <TargetConditionals.h>
  17. //#import <GoogleAPIClientForREST/GTLRRuntimeCommon.h>
  18. #import "GTLRRuntimeCommon.h"
  19. //#import <GoogleAPIClientForREST/GTLRDateTime.h>
  20. //#import <GoogleAPIClientForREST/GTLRDuration.h>
  21. //#import <GoogleAPIClientForREST/GTLRUtilities.h>
  22. #import "GTLRDateTime.h"
  23. #import "GTLRDuration.h"
  24. #import "GTLRUtilities.h"
  25. #import "GTLRDefines.h"
  26. // This can be redefined via a prefix if you are prefixing symbols to prefix the
  27. // names used in strings. Something like:
  28. // #define _HELPER(x) "MyPrefix" #x
  29. // #define GTLR_CLASSNAME_CSTR(x) _HELPER(x)
  30. #ifndef GTLR_CLASSNAME_CSTR
  31. #define _GTLR_CLASSNAME_HELPER(x) #x
  32. #define GTLR_CLASSNAME_CSTR(x) _GTLR_CLASSNAME_HELPER(x)
  33. #endif
  34. // Note: NSObject's class is used as a marker for the expected/default class
  35. // when Discovery says it can be any type of object.
  36. @implementation GTLRRuntimeCommon
  37. // Helper to generically convert JSON to an api object type.
  38. + (id)objectFromJSON:(id)json
  39. defaultClass:(Class)defaultClass
  40. objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver
  41. isCacheable:(BOOL*)isCacheable {
  42. id result = nil;
  43. BOOL canBeCached = YES;
  44. // TODO(TVL): use defaultClass to validate things like expectedClass is
  45. // done in jsonFromAPIObject:expectedClass:isCacheable:?
  46. if ([json isKindOfClass:[NSDictionary class]]) {
  47. // If no default, or the default was any object, then default to base
  48. // object here (and hope there is a kind to get the right thing).
  49. if ((defaultClass == Nil) || [defaultClass isEqual:[NSObject class]]) {
  50. defaultClass = [GTLRObject class];
  51. }
  52. result = [GTLRObject objectForJSON:json
  53. defaultClass:defaultClass
  54. objectClassResolver:objectClassResolver];
  55. } else if ([json isKindOfClass:[NSArray class]]) {
  56. NSArray *jsonArray = json;
  57. // make an object for each JSON dictionary in the array
  58. NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:jsonArray.count];
  59. for (id jsonItem in jsonArray) {
  60. id item = [self objectFromJSON:jsonItem
  61. defaultClass:defaultClass
  62. objectClassResolver:objectClassResolver
  63. isCacheable:NULL];
  64. [resultArray addObject:item];
  65. }
  66. result = resultArray;
  67. } else if ([json isKindOfClass:[NSString class]]) {
  68. // DateTimes and Durations live in JSON as strings, so convert.
  69. if ([defaultClass isEqual:[GTLRDateTime class]]) {
  70. result = [GTLRDateTime dateTimeWithRFC3339String:json];
  71. } else if ([defaultClass isEqual:[GTLRDuration class]]) {
  72. result = [GTLRDuration durationWithJSONString:json];
  73. } else if ([defaultClass isEqual:[NSNumber class]]) {
  74. result = GTLR_EnsureNSNumber(json);
  75. canBeCached = NO;
  76. } else {
  77. result = json;
  78. canBeCached = NO;
  79. }
  80. } else if ([json isKindOfClass:[NSNumber class]] ||
  81. [json isKindOfClass:[NSNull class]]) {
  82. result = json;
  83. canBeCached = NO;
  84. } else {
  85. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: unsupported class '%s' in objectFromJSON",
  86. class_getName([json class]));
  87. }
  88. if (isCacheable) {
  89. *isCacheable = canBeCached;
  90. }
  91. return result;
  92. }
  93. // Helper to generically convert an api object type to JSON.
  94. // |expectedClass| is the type that was expected for |obj|.
  95. + (id)jsonFromAPIObject:(id)obj
  96. expectedClass:(Class)expectedClass
  97. isCacheable:(BOOL *)isCacheable {
  98. id result = nil;
  99. BOOL canBeCached = YES;
  100. BOOL checkExpected = (expectedClass != Nil);
  101. if ([obj isKindOfClass:[NSString class]]) {
  102. result = [obj copy];
  103. canBeCached = NO;
  104. } else if ([obj isKindOfClass:[NSNumber class]] ||
  105. [obj isKindOfClass:[NSNull class]]) {
  106. result = obj;
  107. canBeCached = NO;
  108. } else if ([obj isKindOfClass:[GTLRObject class]]) {
  109. result = [(GTLRObject *)obj JSON];
  110. if (result == nil) {
  111. // adding an empty object; it should have a JSON dictionary so it can
  112. // hold future assignments
  113. [(GTLRObject *)obj setJSON:[NSMutableDictionary dictionary]];
  114. result = [(GTLRObject *)obj JSON];
  115. }
  116. } else if ([obj isKindOfClass:[NSArray class]]) {
  117. checkExpected = NO;
  118. NSArray *array = obj;
  119. // get the JSON for each thing in the array
  120. NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:array.count];
  121. for (id item in array) {
  122. id itemJSON = [self jsonFromAPIObject:item
  123. expectedClass:expectedClass
  124. isCacheable:NULL];
  125. [resultArray addObject:itemJSON];
  126. }
  127. result = resultArray;
  128. } else if ([obj isKindOfClass:[GTLRDateTime class]]) {
  129. // DateTimes live in JSON as strings, so convert.
  130. GTLRDateTime *dateTime = obj;
  131. result = dateTime.RFC3339String;
  132. } else if ([obj isKindOfClass:[GTLRDuration class]]) {
  133. // Durations live in JSON as strings, so convert.
  134. GTLRDuration *duration = obj;
  135. result = duration.jsonString;
  136. } else {
  137. checkExpected = NO;
  138. if (obj) {
  139. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: unsupported class '%s' in jsonFromAPIObject",
  140. class_getName([obj class]));
  141. }
  142. }
  143. if (checkExpected) {
  144. // If the default was any object, then clear it to skip validation checks.
  145. if ([expectedClass isEqual:[NSObject class]] ||
  146. [obj isKindOfClass:[NSNull class]]) {
  147. expectedClass = nil;
  148. }
  149. if (expectedClass && ![obj isKindOfClass:expectedClass]) {
  150. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: jsonFromAPIObject expected class '%s' instead got '%s'",
  151. class_getName(expectedClass), class_getName([obj class]));
  152. }
  153. }
  154. if (isCacheable) {
  155. *isCacheable = canBeCached;
  156. }
  157. return result;
  158. }
  159. + (NSDictionary *)mergedClassDictionaryForSelector:(SEL)selector
  160. startClass:(Class)startClass
  161. ancestorClass:(Class)ancestorClass
  162. cache:(NSMutableDictionary *)cache {
  163. NSDictionary *result;
  164. @synchronized(cache) {
  165. result = [cache objectForKey:startClass];
  166. if (result == nil) {
  167. // Collect the class's dictionary.
  168. #pragma clang diagnostic push
  169. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  170. NSDictionary *classDict = [startClass performSelector:selector];
  171. #pragma clang diagnostic pop
  172. // Collect the parent class's merged dictionary.
  173. NSDictionary *parentClassMergedDict;
  174. if ([startClass isEqual:ancestorClass]) {
  175. parentClassMergedDict = nil;
  176. } else {
  177. Class parentClass = class_getSuperclass(startClass);
  178. parentClassMergedDict =
  179. [self mergedClassDictionaryForSelector:selector
  180. startClass:parentClass
  181. ancestorClass:ancestorClass
  182. cache:cache];
  183. }
  184. // Merge this class's into the parent's so things properly override.
  185. NSMutableDictionary *mergeDict;
  186. if (parentClassMergedDict != nil) {
  187. mergeDict =
  188. [NSMutableDictionary dictionaryWithDictionary:parentClassMergedDict];
  189. } else {
  190. mergeDict = [NSMutableDictionary dictionary];
  191. }
  192. if (classDict != nil) {
  193. [mergeDict addEntriesFromDictionary:classDict];
  194. }
  195. // Make an immutable version.
  196. result = [NSDictionary dictionaryWithDictionary:mergeDict];
  197. // Save it.
  198. [cache setObject:result forKey:(id<NSCopying>)startClass];
  199. }
  200. }
  201. return result;
  202. }
  203. #pragma mark Runtime lookup support
  204. static objc_property_t PropertyForSel(Class<GTLRRuntimeCommon> startClass,
  205. SEL sel, BOOL isSetter,
  206. Class<GTLRRuntimeCommon> *outFoundClass) {
  207. const char *selName = sel_getName(sel);
  208. const char *baseName = selName;
  209. size_t baseNameLen = strlen(baseName);
  210. if (isSetter) {
  211. baseName += 3; // skip "set"
  212. baseNameLen -= 4; // subtract "set" and the final colon
  213. }
  214. // walk from this class up the hierarchy to the ancestor class
  215. Class<GTLRRuntimeCommon> topClass = class_getSuperclass([startClass ancestorClass]);
  216. for (Class currClass = startClass;
  217. currClass != topClass;
  218. currClass = class_getSuperclass(currClass)) {
  219. // step through this class's properties
  220. objc_property_t foundProp = NULL;
  221. objc_property_t *properties = class_copyPropertyList(currClass, NULL);
  222. if (properties) {
  223. for (objc_property_t *prop = properties; *prop != NULL; ++prop) {
  224. const char *propAttrs = property_getAttributes(*prop);
  225. const char *dynamicMarker = strstr(propAttrs, ",D");
  226. if (!dynamicMarker ||
  227. (dynamicMarker[2] != 0 && dynamicMarker[2] != ',' )) {
  228. // It isn't dynamic, skip it.
  229. continue;
  230. }
  231. if (!isSetter) {
  232. // See if this property has an explicit getter=. (the attributes always start with a T,
  233. // so we can check for the leading ','.
  234. const char *getterMarker = strstr(propAttrs, ",G");
  235. if (getterMarker) {
  236. const char *getterStart = getterMarker + 2;
  237. const char *getterEnd = getterStart;
  238. while ((*getterEnd != 0) && (*getterEnd != ',')) {
  239. ++getterEnd;
  240. }
  241. size_t getterLen = (size_t)(getterEnd - getterStart);
  242. if ((strncmp(selName, getterStart, getterLen) == 0)
  243. && (selName[getterLen] == 0)) {
  244. // return the actual property
  245. foundProp = *prop;
  246. // if requested, return the class containing the property
  247. if (outFoundClass) *outFoundClass = currClass;
  248. break;
  249. }
  250. } // if (getterMarker)
  251. } // if (!isSetter)
  252. // Search for an exact-name match (a getter), but case-insensitive on the
  253. // first character (in case baseName comes from a setter)
  254. const char *propName = property_getName(*prop);
  255. size_t propNameLen = strlen(propName);
  256. if (baseNameLen == propNameLen
  257. && strncasecmp(baseName, propName, 1) == 0
  258. && (baseNameLen <= 1
  259. || strncmp(baseName + 1, propName + 1, baseNameLen - 1) == 0)) {
  260. // return the actual property
  261. foundProp = *prop;
  262. // if requested, return the class containing the property
  263. if (outFoundClass) *outFoundClass = currClass;
  264. break;
  265. }
  266. } // for (prop in properties)
  267. free(properties);
  268. }
  269. if (foundProp) return foundProp;
  270. }
  271. // not found; this occasionally happens when the system looks for a method
  272. // like "getFoo" or "descriptionWithLocale:indent:"
  273. return NULL;
  274. }
  275. typedef NS_ENUM(NSUInteger, GTLRPropertyType) {
  276. #if !defined(__LP64__) || !__LP64__
  277. // These two only needed in 32bit builds since NSInteger in 64bit ends up in the LongLong paths.
  278. GTLRPropertyTypeInt32 = 1,
  279. GTLRPropertyTypeUInt32,
  280. #endif
  281. GTLRPropertyTypeLongLong = 3,
  282. GTLRPropertyTypeULongLong,
  283. GTLRPropertyTypeFloat,
  284. GTLRPropertyTypeDouble,
  285. GTLRPropertyTypeBool,
  286. GTLRPropertyTypeNSString,
  287. GTLRPropertyTypeNSNumber,
  288. GTLRPropertyTypeGTLRDateTime,
  289. GTLRPropertyTypeGTLRDuration,
  290. GTLRPropertyTypeNSArray,
  291. GTLRPropertyTypeNSObject,
  292. GTLRPropertyTypeGTLRObject,
  293. };
  294. typedef struct {
  295. const char *attributePrefix;
  296. GTLRPropertyType propertyType;
  297. const char *setterEncoding;
  298. const char *getterEncoding;
  299. // These are the "fixed" return classes, but some properties will require
  300. // looking up the return class instead (because it is a subclass of
  301. // GTLRObject).
  302. const char *returnClassName;
  303. Class returnClass;
  304. BOOL extractReturnClass;
  305. } GTLRDynamicImpInfo;
  306. static const GTLRDynamicImpInfo *DynamicImpInfoForProperty(objc_property_t prop,
  307. Class *outReturnClass) {
  308. if (outReturnClass) *outReturnClass = nil;
  309. // dynamic method resolution:
  310. // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html
  311. //
  312. // property runtimes:
  313. // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
  314. // Get and parse the property attributes, which look something like
  315. // T@"NSString",&,D,P
  316. // Ti,D -- NSInteger on 32bit
  317. // Tq,D -- NSInteger on 64bit, long long on 32bit & 64bit
  318. // TB,D -- BOOL comes as bool on 64bit iOS
  319. // Tc,D -- BOOL comes as char otherwise
  320. // T@"NSString",D
  321. // T@"GTLRLink",D
  322. // T@"NSArray",D
  323. static GTLRDynamicImpInfo kImplInfo[] = {
  324. #if !defined(__LP64__) || !__LP64__
  325. { // NSInteger on 32bit
  326. "Ti",
  327. GTLRPropertyTypeInt32,
  328. "v@:i",
  329. "i@:",
  330. nil, nil,
  331. NO
  332. },
  333. { // NSUInteger on 32bit
  334. "TI",
  335. GTLRPropertyTypeUInt32,
  336. "v@:I",
  337. "I@:",
  338. nil, nil,
  339. NO
  340. },
  341. #endif
  342. { // NSInteger on 64bit, long long on 32bit and 64bit.
  343. "Tq",
  344. GTLRPropertyTypeLongLong,
  345. "v@:q",
  346. "q@:",
  347. nil, nil,
  348. NO
  349. },
  350. { // NSUInteger on 64bit, long long on 32bit and 64bit.
  351. "TQ",
  352. GTLRPropertyTypeULongLong,
  353. "v@:Q",
  354. "Q@:",
  355. nil, nil,
  356. NO
  357. },
  358. { // float
  359. "Tf",
  360. GTLRPropertyTypeFloat,
  361. "v@:f",
  362. "f@:",
  363. nil, nil,
  364. NO
  365. },
  366. { // double
  367. "Td",
  368. GTLRPropertyTypeDouble,
  369. "v@:d",
  370. "d@:",
  371. nil, nil,
  372. NO
  373. },
  374. #if defined(OBJC_BOOL_IS_BOOL) && OBJC_BOOL_IS_BOOL
  375. { // BOOL as bool
  376. "TB",
  377. GTLRPropertyTypeBool,
  378. "v@:B",
  379. "B@:",
  380. nil, nil,
  381. NO
  382. },
  383. #elif defined(OBJC_BOOL_IS_CHAR) && OBJC_BOOL_IS_CHAR
  384. { // BOOL as char
  385. "Tc",
  386. GTLRPropertyTypeBool,
  387. "v@:c",
  388. "c@:",
  389. nil, nil,
  390. NO
  391. },
  392. #else
  393. #error unknown definition for ObjC BOOL type
  394. #endif
  395. { // NSString
  396. "T@\"NSString\"",
  397. GTLRPropertyTypeNSString,
  398. "v@:@",
  399. "@@:",
  400. "NSString", nil,
  401. NO
  402. },
  403. { // NSNumber
  404. "T@\"NSNumber\"",
  405. GTLRPropertyTypeNSNumber,
  406. "v@:@",
  407. "@@:",
  408. "NSNumber", nil,
  409. NO
  410. },
  411. { // GTLRDateTime
  412. "T@\"" GTLR_CLASSNAME_CSTR(GTLRDateTime) "\"",
  413. GTLRPropertyTypeGTLRDateTime,
  414. "v@:@",
  415. "@@:",
  416. GTLR_CLASSNAME_CSTR(GTLRDateTime), nil,
  417. NO
  418. },
  419. { // GTLRDuration
  420. "T@\"" GTLR_CLASSNAME_CSTR(GTLRDuration) "\"",
  421. GTLRPropertyTypeGTLRDuration,
  422. "v@:@",
  423. "@@:",
  424. GTLR_CLASSNAME_CSTR(GTLRDuration), nil,
  425. NO
  426. },
  427. { // NSArray with type
  428. "T@\"NSArray\"",
  429. GTLRPropertyTypeNSArray,
  430. "v@:@",
  431. "@@:",
  432. "NSArray", nil,
  433. NO
  434. },
  435. { // id (any of the objects above)
  436. "T@,",
  437. GTLRPropertyTypeNSObject,
  438. "v@:@",
  439. "@@:",
  440. "NSObject", nil,
  441. NO
  442. },
  443. { // GTLRObject - Last, cause it's a special case and prefix is general
  444. "T@\"",
  445. GTLRPropertyTypeGTLRObject,
  446. "v@:@",
  447. "@@:",
  448. nil, nil,
  449. YES
  450. },
  451. };
  452. static BOOL hasLookedUpClasses = NO;
  453. if (!hasLookedUpClasses) {
  454. // Unfortunately, you can't put [NSString class] into the static structure,
  455. // so this lookup has to be done at runtime.
  456. hasLookedUpClasses = YES;
  457. for (uint32_t idx = 0; idx < sizeof(kImplInfo)/sizeof(kImplInfo[0]); ++idx) {
  458. if (kImplInfo[idx].returnClassName) {
  459. kImplInfo[idx].returnClass = objc_getClass(kImplInfo[idx].returnClassName);
  460. NSCAssert1(kImplInfo[idx].returnClass != nil,
  461. @"GTLRRuntimeCommon: class lookup failed: %s", kImplInfo[idx].returnClassName);
  462. }
  463. }
  464. }
  465. const char *attr = property_getAttributes(prop);
  466. const char *dynamicMarker = strstr(attr, ",D");
  467. if (!dynamicMarker ||
  468. (dynamicMarker[2] != 0 && dynamicMarker[2] != ',' )) {
  469. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: property %s isn't dynamic, attributes %s",
  470. property_getName(prop), attr ? attr : "(nil)");
  471. return NULL;
  472. }
  473. const GTLRDynamicImpInfo *result = NULL;
  474. // Cycle over the list
  475. for (uint32_t idx = 0; idx < sizeof(kImplInfo)/sizeof(kImplInfo[0]); ++idx) {
  476. const char *attributePrefix = kImplInfo[idx].attributePrefix;
  477. if (strncmp(attr, attributePrefix, strlen(attributePrefix)) == 0) {
  478. result = &kImplInfo[idx];
  479. if (outReturnClass) *outReturnClass = result->returnClass;
  480. break;
  481. }
  482. }
  483. if (result == NULL) {
  484. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: unexpected attributes %s for property %s",
  485. attr ? attr : "(nil)", property_getName(prop));
  486. return NULL;
  487. }
  488. if (result->extractReturnClass && outReturnClass) {
  489. // add a null at the next quotation mark
  490. char *attrCopy = strdup(attr);
  491. char *classNameStart = attrCopy + 3;
  492. char *classNameEnd = strstr(classNameStart, "\"");
  493. if (classNameEnd) {
  494. *classNameEnd = '\0';
  495. // Lookup the return class
  496. *outReturnClass = objc_getClass(classNameStart);
  497. if (*outReturnClass == nil) {
  498. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: did not find class with name \"%s\" "
  499. @"for property \"%s\" with attributes \"%s\"",
  500. classNameStart, property_getName(prop), attr);
  501. }
  502. } else {
  503. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: Failed to find end of class name for "
  504. @"property \"%s\" with attributes \"%s\"",
  505. property_getName(prop), attr);
  506. }
  507. free(attrCopy);
  508. }
  509. return result;
  510. }
  511. // Helper to get the IMP for wiring up the getters.
  512. // NOTE: Every argument passed in should be safe to capture in a block. Avoid
  513. // passing something like selName instead of sel, because nothing says that
  514. // pointer will be valid when it is finally used when the method IMP is invoked
  515. // some time later.
  516. static IMP GTLRRuntimeGetterIMP(SEL sel,
  517. GTLRPropertyType propertyType,
  518. NSString *jsonKey,
  519. Class containedClass,
  520. Class returnClass) {
  521. // Only used in DEBUG logging.
  522. #pragma unused(sel)
  523. IMP resultIMP;
  524. switch (propertyType) {
  525. #if !defined(__LP64__) || !__LP64__
  526. case GTLRPropertyTypeInt32: {
  527. resultIMP = imp_implementationWithBlock(^(id obj) {
  528. NSNumber *num = [obj JSONValueForKey:jsonKey];
  529. num = GTLR_EnsureNSNumber(num);
  530. NSInteger result = num.integerValue;
  531. return result;
  532. });
  533. break;
  534. }
  535. case GTLRPropertyTypeUInt32: {
  536. resultIMP = imp_implementationWithBlock(^(id obj) {
  537. NSNumber *num = [obj JSONValueForKey:jsonKey];
  538. num = GTLR_EnsureNSNumber(num);
  539. NSUInteger result = num.unsignedIntegerValue;
  540. return result;
  541. });
  542. break;
  543. }
  544. #endif // __LP64__
  545. case GTLRPropertyTypeLongLong: {
  546. resultIMP = imp_implementationWithBlock(^(id obj) {
  547. NSNumber *num = [obj JSONValueForKey:jsonKey];
  548. num = GTLR_EnsureNSNumber(num);
  549. long long result = num.longLongValue;
  550. return result;
  551. });
  552. break;
  553. }
  554. case GTLRPropertyTypeULongLong: {
  555. resultIMP = imp_implementationWithBlock(^(id obj) {
  556. NSNumber *num = [obj JSONValueForKey:jsonKey];
  557. num = GTLR_EnsureNSNumber(num);
  558. unsigned long long result = num.unsignedLongLongValue;
  559. return result;
  560. });
  561. break;
  562. }
  563. case GTLRPropertyTypeFloat: {
  564. resultIMP = imp_implementationWithBlock(^(id obj) {
  565. NSNumber *num = [obj JSONValueForKey:jsonKey];
  566. num = GTLR_EnsureNSNumber(num);
  567. float result = num.floatValue;
  568. return result;
  569. });
  570. break;
  571. }
  572. case GTLRPropertyTypeDouble: {
  573. resultIMP = imp_implementationWithBlock(^(id obj) {
  574. NSNumber *num = [obj JSONValueForKey:jsonKey];
  575. num = GTLR_EnsureNSNumber(num);
  576. double result = num.doubleValue;
  577. return result;
  578. });
  579. break;
  580. }
  581. case GTLRPropertyTypeBool: {
  582. resultIMP = imp_implementationWithBlock(^(id obj) {
  583. NSNumber *num = [obj JSONValueForKey:jsonKey];
  584. BOOL flag = num.boolValue;
  585. return flag;
  586. });
  587. break;
  588. }
  589. case GTLRPropertyTypeNSString: {
  590. resultIMP = imp_implementationWithBlock(^(id obj) {
  591. NSString *str = [obj JSONValueForKey:jsonKey];
  592. return str;
  593. });
  594. break;
  595. }
  596. case GTLRPropertyTypeGTLRDateTime: {
  597. resultIMP = imp_implementationWithBlock(^GTLRDateTime *(GTLRObject<GTLRRuntimeCommon> *obj) {
  598. // Return the cached object before creating on demand.
  599. GTLRDateTime *cachedDateTime = [obj cacheChildForKey:jsonKey];
  600. if (cachedDateTime != nil) {
  601. return cachedDateTime;
  602. }
  603. NSString *str = [obj JSONValueForKey:jsonKey];
  604. id cacheValue, resultValue;
  605. if (![str isKindOfClass:[NSNull class]]) {
  606. GTLRDateTime *dateTime = [GTLRDateTime dateTimeWithRFC3339String:str];
  607. cacheValue = dateTime;
  608. resultValue = dateTime;
  609. } else {
  610. cacheValue = nil;
  611. resultValue = [NSNull null];
  612. }
  613. [obj setCacheChild:cacheValue forKey:jsonKey];
  614. return resultValue;
  615. });
  616. break;
  617. }
  618. case GTLRPropertyTypeGTLRDuration: {
  619. resultIMP = imp_implementationWithBlock(^GTLRDuration *(GTLRObject<GTLRRuntimeCommon> *obj) {
  620. // Return the cached object before creating on demand.
  621. GTLRDuration *cachedDuration = [obj cacheChildForKey:jsonKey];
  622. if (cachedDuration != nil) {
  623. return cachedDuration;
  624. }
  625. NSString *str = [obj JSONValueForKey:jsonKey];
  626. id cacheValue, resultValue;
  627. if (![str isKindOfClass:[NSNull class]]) {
  628. GTLRDuration *duration = [GTLRDuration durationWithJSONString:str];
  629. cacheValue = duration;
  630. resultValue = duration;
  631. } else {
  632. cacheValue = nil;
  633. resultValue = [NSNull null];
  634. }
  635. [obj setCacheChild:cacheValue forKey:jsonKey];
  636. return resultValue;
  637. });
  638. break;
  639. }
  640. case GTLRPropertyTypeNSNumber: {
  641. resultIMP = imp_implementationWithBlock(^(id obj) {
  642. NSNumber *num = [obj JSONValueForKey:jsonKey];
  643. num = GTLR_EnsureNSNumber(num);
  644. return num;
  645. });
  646. break;
  647. }
  648. case GTLRPropertyTypeGTLRObject: {
  649. // Default return class to GTLRObject if it wasn't found.
  650. if (returnClass == Nil) {
  651. returnClass = [GTLRObject class];
  652. }
  653. resultIMP = imp_implementationWithBlock(^GTLRObject *(GTLRObject<GTLRRuntimeCommon> *obj) {
  654. // Return the cached object before creating on demand.
  655. GTLRObject *cachedObj = [obj cacheChildForKey:jsonKey];
  656. if (cachedObj != nil) {
  657. return cachedObj;
  658. }
  659. NSMutableDictionary *dict = [obj JSONValueForKey:jsonKey];
  660. if ([dict isKindOfClass:[NSMutableDictionary class]]) {
  661. id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver];
  662. GTLRObject *subObj = [GTLRObject objectForJSON:dict
  663. defaultClass:returnClass
  664. objectClassResolver:objectClassResolver];
  665. [obj setCacheChild:subObj forKey:jsonKey];
  666. return subObj;
  667. } else if ([dict isKindOfClass:[NSNull class]]) {
  668. [obj setCacheChild:nil forKey:jsonKey];
  669. return (GTLRObject*)[NSNull null];
  670. } else if (dict != nil) {
  671. // unexpected; probably got a string -- let the caller figure it out
  672. GTLR_DEBUG_LOG(@"GTLRObject: unexpected JSON: %@.%@ should be a dictionary, actually is a %@:\n%@",
  673. NSStringFromClass([obj class]),
  674. NSStringFromSelector(sel),
  675. NSStringFromClass([dict class]), dict);
  676. return (GTLRObject *)dict;
  677. }
  678. return nil;
  679. });
  680. break;
  681. }
  682. case GTLRPropertyTypeNSArray: {
  683. resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj) {
  684. // Return the cached array before creating on demand.
  685. NSMutableArray *cachedArray = [obj cacheChildForKey:jsonKey];
  686. if (cachedArray != nil) {
  687. return cachedArray;
  688. }
  689. NSMutableArray *result = nil;
  690. NSArray *array = [obj JSONValueForKey:jsonKey];
  691. if (array != nil) {
  692. if ([array isKindOfClass:[NSArray class]]) {
  693. id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver];
  694. result = [GTLRRuntimeCommon objectFromJSON:array
  695. defaultClass:containedClass
  696. objectClassResolver:objectClassResolver
  697. isCacheable:NULL];
  698. } else {
  699. #if DEBUG
  700. if (![array isKindOfClass:[NSNull class]]) {
  701. GTLR_DEBUG_LOG(@"GTLRObject: unexpected JSON: %@.%@ should be an array, actually is a %@:\n%@",
  702. NSStringFromClass([obj class]),
  703. NSStringFromSelector(sel),
  704. NSStringFromClass([array class]), array);
  705. }
  706. #endif
  707. result = (NSMutableArray *)array;
  708. }
  709. }
  710. [obj setCacheChild:result forKey:jsonKey];
  711. return result;
  712. });
  713. break;
  714. }
  715. case GTLRPropertyTypeNSObject: {
  716. resultIMP = imp_implementationWithBlock(^id(GTLRObject<GTLRRuntimeCommon> *obj) {
  717. // Return the cached object before creating on demand.
  718. id cachedObj = [obj cacheChildForKey:jsonKey];
  719. if (cachedObj != nil) {
  720. return cachedObj;
  721. }
  722. id jsonObj = [obj JSONValueForKey:jsonKey];
  723. if (jsonObj != nil) {
  724. BOOL shouldCache = NO;
  725. id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver];
  726. id result = [GTLRRuntimeCommon objectFromJSON:jsonObj
  727. defaultClass:nil
  728. objectClassResolver:objectClassResolver
  729. isCacheable:&shouldCache];
  730. [obj setCacheChild:(shouldCache ? result : nil)
  731. forKey:jsonKey];
  732. return result;
  733. }
  734. return nil;
  735. });
  736. break;
  737. }
  738. } // switch(propertyType)
  739. return resultIMP;
  740. }
  741. // Helper to get the IMP for wiring up the setters.
  742. // NOTE: Every argument passed in should be safe to capture in a block. Avoid
  743. // passing something like selName instead of sel, because nothing says that
  744. // pointer will be valid when it is finally used when the method IMP is invoked
  745. // some time later.
  746. static IMP GTLRRuntimeSetterIMP(SEL sel,
  747. GTLRPropertyType propertyType,
  748. NSString *jsonKey,
  749. Class containedClass,
  750. Class returnClass) {
  751. #pragma unused(sel, returnClass)
  752. IMP resultIMP;
  753. switch (propertyType) {
  754. #if !defined(__LP64__) || !__LP64__
  755. case GTLRPropertyTypeInt32: {
  756. resultIMP = imp_implementationWithBlock(^(id obj, NSInteger val) {
  757. [obj setJSONValue:@(val) forKey:jsonKey];
  758. });
  759. break;
  760. }
  761. case GTLRPropertyTypeUInt32: {
  762. resultIMP = imp_implementationWithBlock(^(id obj, NSUInteger val) {
  763. [obj setJSONValue:@(val) forKey:jsonKey];
  764. });
  765. break;
  766. }
  767. #endif // __LP64__
  768. case GTLRPropertyTypeLongLong: {
  769. resultIMP = imp_implementationWithBlock(^(id obj, long long val) {
  770. [obj setJSONValue:@(val) forKey:jsonKey];
  771. });
  772. break;
  773. }
  774. case GTLRPropertyTypeULongLong: {
  775. resultIMP = imp_implementationWithBlock(^(id obj,
  776. unsigned long long val) {
  777. [obj setJSONValue:@(val) forKey:jsonKey];
  778. });
  779. break;
  780. }
  781. case GTLRPropertyTypeFloat: {
  782. resultIMP = imp_implementationWithBlock(^(id obj, float val) {
  783. [obj setJSONValue:@(val) forKey:jsonKey];
  784. });
  785. break;
  786. }
  787. case GTLRPropertyTypeDouble: {
  788. resultIMP = imp_implementationWithBlock(^(id obj, double val) {
  789. [obj setJSONValue:@(val) forKey:jsonKey];
  790. });
  791. break;
  792. }
  793. case GTLRPropertyTypeBool: {
  794. resultIMP = imp_implementationWithBlock(^(id obj, BOOL val) {
  795. NSNumber *numValue = (NSNumber *)(val ? kCFBooleanTrue : kCFBooleanFalse);
  796. [obj setJSONValue:numValue forKey:jsonKey];
  797. });
  798. break;
  799. }
  800. case GTLRPropertyTypeNSString: {
  801. resultIMP = imp_implementationWithBlock(^(id obj, NSString *val) {
  802. NSString *copiedStr = [val copy];
  803. [obj setJSONValue:copiedStr forKey:jsonKey];
  804. });
  805. break;
  806. }
  807. case GTLRPropertyTypeGTLRDateTime: {
  808. resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
  809. GTLRDateTime *val) {
  810. id cacheValue, jsonValue;
  811. if (![val isKindOfClass:[NSNull class]]) {
  812. jsonValue = val.RFC3339String;
  813. cacheValue = val;
  814. } else {
  815. jsonValue = [NSNull null];
  816. cacheValue = nil;
  817. }
  818. [obj setJSONValue:jsonValue forKey:jsonKey];
  819. [obj setCacheChild:cacheValue forKey:jsonKey];
  820. });
  821. break;
  822. }
  823. case GTLRPropertyTypeGTLRDuration: {
  824. resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
  825. GTLRDuration *val) {
  826. id cacheValue, jsonValue;
  827. if (![val isKindOfClass:[NSNull class]]) {
  828. jsonValue = val.jsonString;
  829. cacheValue = val;
  830. } else {
  831. jsonValue = [NSNull null];
  832. cacheValue = nil;
  833. }
  834. [obj setJSONValue:jsonValue forKey:jsonKey];
  835. [obj setCacheChild:cacheValue forKey:jsonKey];
  836. });
  837. break;
  838. }
  839. case GTLRPropertyTypeNSNumber: {
  840. resultIMP = imp_implementationWithBlock(^(id obj, NSNumber *val) {
  841. [obj setJSONValue:val forKey:jsonKey];
  842. });
  843. break;
  844. }
  845. case GTLRPropertyTypeGTLRObject: {
  846. resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
  847. GTLRObject *val) {
  848. id cacheValue, jsonValue;
  849. if (![val isKindOfClass:[NSNull class]]) {
  850. NSMutableDictionary *dict = [val JSON];
  851. if (dict == nil && val != nil) {
  852. // adding an empty object; it should have a JSON dictionary so it
  853. // can hold future assignments
  854. val.JSON = [NSMutableDictionary dictionary];
  855. jsonValue = val.JSON;
  856. } else {
  857. jsonValue = dict;
  858. }
  859. cacheValue = val;
  860. } else {
  861. jsonValue = [NSNull null];
  862. cacheValue = nil;
  863. }
  864. [obj setJSONValue:jsonValue forKey:jsonKey];
  865. [obj setCacheChild:cacheValue forKey:jsonKey];
  866. });
  867. break;
  868. }
  869. case GTLRPropertyTypeNSArray: {
  870. resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
  871. NSMutableArray *val) {
  872. id json = [GTLRRuntimeCommon jsonFromAPIObject:val
  873. expectedClass:containedClass
  874. isCacheable:NULL];
  875. [obj setJSONValue:json forKey:jsonKey];
  876. [obj setCacheChild:val forKey:jsonKey];
  877. });
  878. break;
  879. }
  880. case GTLRPropertyTypeNSObject: {
  881. resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj,
  882. id val) {
  883. BOOL shouldCache = NO;
  884. id json = [GTLRRuntimeCommon jsonFromAPIObject:val
  885. expectedClass:Nil
  886. isCacheable:&shouldCache];
  887. [obj setJSONValue:json forKey:jsonKey];
  888. [obj setCacheChild:(shouldCache ? val : nil)
  889. forKey:jsonKey];
  890. });
  891. break;
  892. }
  893. } // switch(propertyType)
  894. return resultIMP;
  895. }
  896. #pragma mark Runtime - wiring point
  897. + (BOOL)resolveInstanceMethod:(SEL)sel onClass:(Class<GTLRRuntimeCommon>)onClass {
  898. // dynamic method resolution:
  899. // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html
  900. //
  901. // property runtimes:
  902. // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
  903. const char *selName = sel_getName(sel);
  904. size_t selNameLen = strlen(selName);
  905. char lastChar = selName[selNameLen - 1];
  906. BOOL isSetter = (lastChar == ':');
  907. // look for a declared property matching this selector name exactly
  908. Class<GTLRRuntimeCommon> foundClass = nil;
  909. objc_property_t prop = PropertyForSel(onClass, sel, isSetter, &foundClass);
  910. if (prop == NULL || foundClass == nil) {
  911. return NO; // No luck, out of here.
  912. }
  913. Class returnClass = nil;
  914. const GTLRDynamicImpInfo *implInfo = DynamicImpInfoForProperty(prop,
  915. &returnClass);
  916. if (implInfo == NULL) {
  917. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: unexpected return type class %s for "
  918. @"property \"%s\" of class \"%s\"",
  919. returnClass ? class_getName(returnClass) : "<nil>",
  920. property_getName(prop),
  921. class_getName(onClass));
  922. return NO; // Failed to find our impl info, out of here.
  923. }
  924. const char *propName = property_getName(prop);
  925. NSString *propStr = @(propName);
  926. // replace the property name with the proper JSON key if it's
  927. // special-cased with a map in the found class; otherwise, the property
  928. // name is the JSON key
  929. // NOTE: These caches that are built up could likely be dropped and do this
  930. // lookup on demand from the class tree. Most are checked once when a method
  931. // is first resolved, so eventually become wasted memory.
  932. NSDictionary *keyMap =
  933. [[foundClass ancestorClass] propertyToJSONKeyMapForClass:foundClass];
  934. NSString *jsonKey = [keyMap objectForKey:propStr];
  935. if (jsonKey == nil) {
  936. jsonKey = propStr;
  937. }
  938. // For arrays we need to look up what the contained class is.
  939. Class containedClass = nil;
  940. if (implInfo->propertyType == GTLRPropertyTypeNSArray) {
  941. NSDictionary *classMap =
  942. [[foundClass ancestorClass] arrayPropertyToClassMapForClass:foundClass];
  943. containedClass = [classMap objectForKey:jsonKey];
  944. if (containedClass == Nil) {
  945. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: expected array item class for "
  946. @"property \"%s\" of class \"%s\"",
  947. property_getName(prop), class_getName(foundClass));
  948. }
  949. }
  950. // Wire in the method.
  951. IMP imp;
  952. const char *encoding;
  953. if (isSetter) {
  954. imp = GTLRRuntimeSetterIMP(sel, implInfo->propertyType,
  955. jsonKey, containedClass, returnClass);
  956. encoding = implInfo->setterEncoding;
  957. } else {
  958. imp = GTLRRuntimeGetterIMP(sel, implInfo->propertyType,
  959. jsonKey, containedClass, returnClass);
  960. encoding = implInfo->getterEncoding;
  961. }
  962. if (class_addMethod(foundClass, sel, imp, encoding)) {
  963. return YES;
  964. }
  965. // Not much we can do if this fails, but leave a breadcumb in the log.
  966. GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: Failed to wire %@ on %@ (encoding: %s).",
  967. NSStringFromSelector(sel),
  968. NSStringFromClass(foundClass),
  969. encoding);
  970. return NO;
  971. }
  972. @end