FLEXRuntimeUtility.m 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. //
  2. // FLEXRuntimeUtility.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 6/8/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import <UIKit/UIKit.h>
  9. #import "FLEXRuntimeUtility.h"
  10. #import "FLEXObjcInternal.h"
  11. // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
  12. NSString *const kFLEXUtilityAttributeTypeEncoding = @"T";
  13. NSString *const kFLEXUtilityAttributeBackingIvar = @"V";
  14. NSString *const kFLEXUtilityAttributeReadOnly = @"R";
  15. NSString *const kFLEXUtilityAttributeCopy = @"C";
  16. NSString *const kFLEXUtilityAttributeRetain = @"&";
  17. NSString *const kFLEXUtilityAttributeNonAtomic = @"N";
  18. NSString *const kFLEXUtilityAttributeCustomGetter = @"G";
  19. NSString *const kFLEXUtilityAttributeCustomSetter = @"S";
  20. NSString *const kFLEXUtilityAttributeDynamic = @"D";
  21. NSString *const kFLEXUtilityAttributeWeak = @"W";
  22. NSString *const kFLEXUtilityAttributeGarbageCollectable = @"P";
  23. NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding = @"t";
  24. static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
  25. typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
  26. FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0,
  27. FLEXRuntimeUtilityErrorCodeInvocationFailed = 1,
  28. FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch = 2
  29. };
  30. // Arguments 0 and 1 are self and _cmd always
  31. const unsigned int kFLEXNumberOfImplicitArgs = 2;
  32. @implementation FLEXRuntimeUtility
  33. #pragma mark - General Helpers (Public)
  34. + (BOOL)pointerIsValidObjcObject:(const void *)pointer
  35. {
  36. return FLEXPointerIsValidObjcObject(pointer);
  37. }
  38. + (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType
  39. {
  40. if (!returnedObjectOrNil) {
  41. return nil;
  42. }
  43. NSInteger i = 0;
  44. if (returnType[i] == FLEXTypeEncodingConst) {
  45. i++;
  46. }
  47. BOOL returnsObjectOrClass = returnType[i] == FLEXTypeEncodingObjcObject ||
  48. returnType[i] == FLEXTypeEncodingObjcClass;
  49. BOOL returnsVoidPointer = returnType[i] == FLEXTypeEncodingPointer &&
  50. returnType[i+1] == FLEXTypeEncodingVoid;
  51. BOOL returnsCString = returnType[i] == FLEXTypeEncodingCString;
  52. // If we got back an NSValue and the return type is not an object,
  53. // we check to see if the pointer is of a valid object. If not,
  54. // we just display the NSValue.
  55. if (!returnsObjectOrClass) {
  56. // Can only be NSValue since return type is not an object,
  57. // so we bail if this doesn't add up
  58. if (![returnedObjectOrNil isKindOfClass:[NSValue class]]) {
  59. return returnedObjectOrNil;
  60. }
  61. NSValue *value = (NSValue *)returnedObjectOrNil;
  62. if (returnsCString) {
  63. // Wrap char * in NSString
  64. const char *string = (const char *)value.pointerValue;
  65. returnedObjectOrNil = string ? [NSString stringWithCString:string encoding:NSUTF8StringEncoding] : NULL;
  66. } else if (returnsVoidPointer) {
  67. // Cast valid objects disguised as void * to id
  68. if ([FLEXRuntimeUtility pointerIsValidObjcObject:value.pointerValue]) {
  69. returnedObjectOrNil = (__bridge id)value.pointerValue;
  70. }
  71. }
  72. }
  73. return returnedObjectOrNil;
  74. }
  75. + (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass
  76. {
  77. NSMutableArray<Class> *superClasses = [NSMutableArray new];
  78. id cls = [objectOrClass class];
  79. do {
  80. [superClasses addObject:cls];
  81. } while ((cls = [cls superclass]));
  82. return superClasses;
  83. }
  84. #pragma mark - Property Helpers (Public)
  85. + (NSString *)prettyNameForProperty:(objc_property_t)property
  86. {
  87. NSString *name = @(property_getName(property));
  88. NSString *encoding = [self typeEncodingForProperty:property];
  89. NSString *readableType = [self readableTypeForEncoding:encoding];
  90. return [self appendName:name toType:readableType];
  91. }
  92. + (NSString *)typeEncodingForProperty:(objc_property_t)property
  93. {
  94. NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesDictionaryForProperty:property];
  95. return attributesDictionary[kFLEXUtilityAttributeTypeEncoding];
  96. }
  97. + (BOOL)isReadonlyProperty:(objc_property_t)property
  98. {
  99. return [[self attributesDictionaryForProperty:property] objectForKey:kFLEXUtilityAttributeReadOnly] != nil;
  100. }
  101. + (SEL)setterSelectorForProperty:(objc_property_t)property
  102. {
  103. SEL setterSelector = NULL;
  104. NSString *setterSelectorString = [[self attributesDictionaryForProperty:property] objectForKey:kFLEXUtilityAttributeCustomSetter];
  105. if (!setterSelectorString) {
  106. NSString *propertyName = @(property_getName(property));
  107. setterSelectorString = [NSString stringWithFormat:@"set%@%@:", [[propertyName substringToIndex:1] uppercaseString], [propertyName substringFromIndex:1]];
  108. }
  109. if (setterSelectorString) {
  110. setterSelector = NSSelectorFromString(setterSelectorString);
  111. }
  112. return setterSelector;
  113. }
  114. + (NSString *)fullDescriptionForProperty:(objc_property_t)property
  115. {
  116. NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesDictionaryForProperty:property];
  117. NSMutableArray<NSString *> *attributesStrings = [NSMutableArray array];
  118. // Atomicity
  119. if (attributesDictionary[kFLEXUtilityAttributeNonAtomic]) {
  120. [attributesStrings addObject:@"nonatomic"];
  121. } else {
  122. [attributesStrings addObject:@"atomic"];
  123. }
  124. // Storage
  125. if (attributesDictionary[kFLEXUtilityAttributeRetain]) {
  126. [attributesStrings addObject:@"strong"];
  127. } else if (attributesDictionary[kFLEXUtilityAttributeCopy]) {
  128. [attributesStrings addObject:@"copy"];
  129. } else if (attributesDictionary[kFLEXUtilityAttributeWeak]) {
  130. [attributesStrings addObject:@"weak"];
  131. } else {
  132. [attributesStrings addObject:@"assign"];
  133. }
  134. // Mutability
  135. if (attributesDictionary[kFLEXUtilityAttributeReadOnly]) {
  136. [attributesStrings addObject:@"readonly"];
  137. } else {
  138. [attributesStrings addObject:@"readwrite"];
  139. }
  140. // Custom getter/setter
  141. NSString *customGetter = attributesDictionary[kFLEXUtilityAttributeCustomGetter];
  142. NSString *customSetter = attributesDictionary[kFLEXUtilityAttributeCustomSetter];
  143. if (customGetter) {
  144. [attributesStrings addObject:[NSString stringWithFormat:@"getter=%@", customGetter]];
  145. }
  146. if (customSetter) {
  147. [attributesStrings addObject:[NSString stringWithFormat:@"setter=%@", customSetter]];
  148. }
  149. NSString *attributesString = [attributesStrings componentsJoinedByString:@", "];
  150. NSString *shortName = [self prettyNameForProperty:property];
  151. return [NSString stringWithFormat:@"@property (%@) %@", attributesString, shortName];
  152. }
  153. + (id)valueForProperty:(objc_property_t)property onObject:(id)object
  154. {
  155. NSString *customGetterString = nil;
  156. char *customGetterName = property_copyAttributeValue(property, kFLEXUtilityAttributeCustomGetter.UTF8String);
  157. if (customGetterName) {
  158. customGetterString = @(customGetterName);
  159. free(customGetterName);
  160. }
  161. SEL getterSelector;
  162. if (customGetterString.length > 0) {
  163. getterSelector = NSSelectorFromString(customGetterString);
  164. } else {
  165. NSString *propertyName = @(property_getName(property));
  166. getterSelector = NSSelectorFromString(propertyName);
  167. }
  168. return [self performSelector:getterSelector onObject:object withArguments:nil error:NULL];
  169. }
  170. + (NSString *)descriptionForIvarOrPropertyValue:(id)value
  171. {
  172. NSString *description = nil;
  173. // Special case BOOL for better readability.
  174. if ([value isKindOfClass:[NSValue class]]) {
  175. const char *type = [value objCType];
  176. if (strcmp(type, @encode(BOOL)) == 0) {
  177. BOOL boolValue = NO;
  178. [value getValue:&boolValue];
  179. description = boolValue ? @"YES" : @"NO";
  180. } else if (strcmp(type, @encode(SEL)) == 0) {
  181. SEL selector = NULL;
  182. [value getValue:&selector];
  183. description = NSStringFromSelector(selector);
  184. }
  185. }
  186. @try {
  187. if (!description) {
  188. // Single line display - replace newlines and tabs with spaces.
  189. description = [[value description] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
  190. description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
  191. }
  192. } @catch (NSException *e) {
  193. description = [@"Thrown: " stringByAppendingString:e.reason ?: @"(nil exception reason)"];
  194. }
  195. if (!description) {
  196. description = @"nil";
  197. }
  198. return description;
  199. }
  200. + (void)tryAddPropertyWithName:(const char *)name attributes:(NSDictionary<NSString *, NSString *> *)attributePairs toClass:(__unsafe_unretained Class)theClass
  201. {
  202. objc_property_t property = class_getProperty(theClass, name);
  203. if (!property) {
  204. unsigned int totalAttributesCount = (unsigned int)attributePairs.count;
  205. objc_property_attribute_t *attributes = malloc(sizeof(objc_property_attribute_t) * totalAttributesCount);
  206. if (attributes) {
  207. unsigned int attributeIndex = 0;
  208. for (NSString *attributeName in attributePairs.allKeys) {
  209. objc_property_attribute_t attribute;
  210. attribute.name = attributeName.UTF8String;
  211. attribute.value = attributePairs[attributeName].UTF8String;
  212. attributes[attributeIndex++] = attribute;
  213. }
  214. class_addProperty(theClass, name, attributes, totalAttributesCount);
  215. free(attributes);
  216. }
  217. }
  218. }
  219. #pragma mark - Ivar Helpers (Public)
  220. + (NSString *)prettyNameForIvar:(Ivar)ivar
  221. {
  222. const char *nameCString = ivar_getName(ivar);
  223. NSString *name = nameCString ? @(nameCString) : nil;
  224. const char *encodingCString = ivar_getTypeEncoding(ivar);
  225. NSString *encoding = encodingCString ? @(encodingCString) : nil;
  226. NSString *readableType = [self readableTypeForEncoding:encoding];
  227. return [self appendName:name toType:readableType];
  228. }
  229. + (id)valueForIvar:(Ivar)ivar onObject:(id)object
  230. {
  231. id value = nil;
  232. const char *type = ivar_getTypeEncoding(ivar);
  233. #ifdef __arm64__
  234. // See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
  235. const char *name = ivar_getName(ivar);
  236. if (type[0] == FLEXTypeEncodingObjcClass && strcmp(name, "isa") == 0) {
  237. value = object_getClass(object);
  238. } else
  239. #endif
  240. if (type[0] == FLEXTypeEncodingObjcObject || type[0] == FLEXTypeEncodingObjcClass) {
  241. value = object_getIvar(object, ivar);
  242. } else {
  243. ptrdiff_t offset = ivar_getOffset(ivar);
  244. void *pointer = (__bridge void *)object + offset;
  245. value = [self valueForPrimitivePointer:pointer objCType:type];
  246. }
  247. return value;
  248. }
  249. + (void)setValue:(id)value forIvar:(Ivar)ivar onObject:(id)object
  250. {
  251. const char *typeEncodingCString = ivar_getTypeEncoding(ivar);
  252. if (typeEncodingCString[0] == FLEXTypeEncodingObjcObject) {
  253. object_setIvar(object, ivar, value);
  254. } else if ([value isKindOfClass:[NSValue class]]) {
  255. // Primitive - unbox the NSValue.
  256. NSValue *valueValue = (NSValue *)value;
  257. // Make sure that the box contained the correct type.
  258. NSAssert(strcmp([valueValue objCType], typeEncodingCString) == 0, @"Type encoding mismatch (value: %s; ivar: %s) in setting ivar named: %s on object: %@", [valueValue objCType], typeEncodingCString, ivar_getName(ivar), object);
  259. NSUInteger bufferSize = 0;
  260. @try {
  261. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  262. NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
  263. } @catch (NSException *exception) { }
  264. if (bufferSize > 0) {
  265. void *buffer = calloc(bufferSize, 1);
  266. [valueValue getValue:buffer];
  267. ptrdiff_t offset = ivar_getOffset(ivar);
  268. void *pointer = (__bridge void *)object + offset;
  269. memcpy(pointer, buffer, bufferSize);
  270. free(buffer);
  271. }
  272. }
  273. }
  274. #pragma mark - Method Helpers (Public)
  275. + (NSString *)prettyNameForMethod:(Method)method isClassMethod:(BOOL)isClassMethod
  276. {
  277. NSString *selectorName = NSStringFromSelector(method_getName(method));
  278. NSString *methodTypeString = isClassMethod ? @"+" : @"-";
  279. char *returnType = method_copyReturnType(method);
  280. NSString *readableReturnType = [self readableTypeForEncoding:@(returnType)];
  281. free(returnType);
  282. NSString *prettyName = [NSString stringWithFormat:@"%@ (%@)", methodTypeString, readableReturnType];
  283. NSArray<NSString *> *components = [self prettyArgumentComponentsForMethod:method];
  284. if (components.count > 0) {
  285. prettyName = [prettyName stringByAppendingString:[components componentsJoinedByString:@" "]];
  286. } else {
  287. prettyName = [prettyName stringByAppendingString:selectorName];
  288. }
  289. return prettyName;
  290. }
  291. + (NSArray<NSString *> *)prettyArgumentComponentsForMethod:(Method)method
  292. {
  293. NSMutableArray<NSString *> *components = [NSMutableArray array];
  294. NSString *selectorName = NSStringFromSelector(method_getName(method));
  295. NSMutableArray<NSString *> *selectorComponents = [[selectorName componentsSeparatedByString:@":"] mutableCopy];
  296. // this is a workaround cause method_getNumberOfArguments() returns wrong number for some methods
  297. if (selectorComponents.count == 1) {
  298. return @[];
  299. }
  300. if ([selectorComponents.lastObject isEqualToString:@""]) {
  301. [selectorComponents removeLastObject];
  302. }
  303. for (unsigned int argIndex = 0; argIndex < selectorComponents.count; argIndex++) {
  304. char *argType = method_copyArgumentType(method, argIndex + kFLEXNumberOfImplicitArgs);
  305. NSString *readableArgType = (argType != NULL) ? [self readableTypeForEncoding:@(argType)] : nil;
  306. free(argType);
  307. NSString *prettyComponent = [NSString stringWithFormat:@"%@:(%@) ", [selectorComponents objectAtIndex:argIndex], readableArgType];
  308. [components addObject:prettyComponent];
  309. }
  310. return components;
  311. }
  312. + (FLEXTypeEncoding *)returnTypeForMethod:(Method)method
  313. {
  314. return (FLEXTypeEncoding *)method_copyReturnType(method);
  315. }
  316. #pragma mark - Method Calling/Field Editing (Public)
  317. + (id)performSelector:(SEL)selector onObject:(id)object withArguments:(NSArray *)arguments error:(NSError * __autoreleasing *)error
  318. {
  319. // Bail if the object won't respond to this selector.
  320. if (![object respondsToSelector:selector]) {
  321. if (error) {
  322. NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@ does not respond to the selector %@", object, NSStringFromSelector(selector)]};
  323. *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector userInfo:userInfo];
  324. }
  325. return nil;
  326. }
  327. // Build the invocation
  328. NSMethodSignature *methodSignature = [object methodSignatureForSelector:selector];
  329. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
  330. [invocation setSelector:selector];
  331. [invocation setTarget:object];
  332. [invocation retainArguments];
  333. // Always self and _cmd
  334. NSUInteger numberOfArguments = [methodSignature numberOfArguments];
  335. for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
  336. NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
  337. id argumentObject = arguments.count > argumentsArrayIndex ? arguments[argumentsArrayIndex] : nil;
  338. // NSNull in the arguments array can be passed as a placeholder to indicate nil. We only need to set the argument if it will be non-nil.
  339. if (argumentObject && ![argumentObject isKindOfClass:[NSNull class]]) {
  340. const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex];
  341. if (typeEncodingCString[0] == FLEXTypeEncodingObjcObject || typeEncodingCString[0] == FLEXTypeEncodingObjcClass || [self isTollFreeBridgedValue:argumentObject forCFType:typeEncodingCString]) {
  342. // Object
  343. [invocation setArgument:&argumentObject atIndex:argumentIndex];
  344. } else if (strcmp(typeEncodingCString, @encode(CGColorRef)) == 0 && [argumentObject isKindOfClass:[UIColor class]]) {
  345. // Bridging UIColor to CGColorRef
  346. CGColorRef colorRef = [argumentObject CGColor];
  347. [invocation setArgument:&colorRef atIndex:argumentIndex];
  348. } else if ([argumentObject isKindOfClass:[NSValue class]]) {
  349. // Primitive boxed in NSValue
  350. NSValue *argumentValue = (NSValue *)argumentObject;
  351. // Ensure that the type encoding on the NSValue matches the type encoding of the argument in the method signature
  352. if (strcmp([argumentValue objCType], typeEncodingCString) != 0) {
  353. if (error) {
  354. NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Type encoding mismatch for argument at index %lu. Value type: %s; Method argument type: %s.", (unsigned long)argumentsArrayIndex, [argumentValue objCType], typeEncodingCString]};
  355. *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch userInfo:userInfo];
  356. }
  357. return nil;
  358. }
  359. @try {
  360. NSUInteger bufferSize = 0;
  361. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  362. NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
  363. if (bufferSize > 0) {
  364. void *buffer = calloc(bufferSize, 1);
  365. [argumentValue getValue:buffer];
  366. [invocation setArgument:buffer atIndex:argumentIndex];
  367. free(buffer);
  368. }
  369. } @catch (NSException *exception) { }
  370. }
  371. }
  372. }
  373. // Try to invoke the invocation but guard against an exception being thrown.
  374. id returnObject = nil;
  375. @try {
  376. // Some methods are not fit to be called...
  377. // Looking at you -[UIResponder(UITextInputAdditions) _caretRect]
  378. [invocation invoke];
  379. // Retrieve the return value and box if necessary.
  380. const char *returnType = methodSignature.methodReturnType;
  381. if (returnType[0] == FLEXTypeEncodingObjcObject || returnType[0] == FLEXTypeEncodingObjcClass) {
  382. // Return value is an object.
  383. __unsafe_unretained id objectReturnedFromMethod = nil;
  384. [invocation getReturnValue:&objectReturnedFromMethod];
  385. returnObject = objectReturnedFromMethod;
  386. } else if (returnType[0] != FLEXTypeEncodingVoid) {
  387. // Will use arbitrary buffer for return value and box it.
  388. void *returnValue = malloc(methodSignature.methodReturnLength);
  389. if (returnValue) {
  390. [invocation getReturnValue:returnValue];
  391. returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
  392. free(returnValue);
  393. }
  394. }
  395. } @catch (NSException *exception) {
  396. // Bummer...
  397. if (error) {
  398. // "… on <class>" / "… on instance of <class>"
  399. NSString *class = NSStringFromClass([object class]);
  400. NSString *calledOn = object == [object class] ? class : [@"an instance of " stringByAppendingString:class];
  401. NSString *message = [NSString stringWithFormat:@"Exception '%@' thrown while performing selector '%@' on %@.\nReason:\n\n%@",
  402. exception.name,
  403. NSStringFromSelector(selector),
  404. calledOn,
  405. exception.reason];
  406. *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain
  407. code:FLEXRuntimeUtilityErrorCodeInvocationFailed
  408. userInfo:@{ NSLocalizedDescriptionKey : message }];
  409. }
  410. }
  411. return returnObject;
  412. }
  413. + (BOOL)isTollFreeBridgedValue:(id)value forCFType:(const char *)typeEncoding
  414. {
  415. // See https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html
  416. #define CASE(cftype, foundationClass) \
  417. if (strcmp(typeEncoding, @encode(cftype)) == 0) { \
  418. return [value isKindOfClass:[foundationClass class]]; \
  419. }
  420. CASE(CFArrayRef, NSArray);
  421. CASE(CFAttributedStringRef, NSAttributedString);
  422. CASE(CFCalendarRef, NSCalendar);
  423. CASE(CFCharacterSetRef, NSCharacterSet);
  424. CASE(CFDataRef, NSData);
  425. CASE(CFDateRef, NSDate);
  426. CASE(CFDictionaryRef, NSDictionary);
  427. CASE(CFErrorRef, NSError);
  428. CASE(CFLocaleRef, NSLocale);
  429. CASE(CFMutableArrayRef, NSMutableArray);
  430. CASE(CFMutableAttributedStringRef, NSMutableAttributedString);
  431. CASE(CFMutableCharacterSetRef, NSMutableCharacterSet);
  432. CASE(CFMutableDataRef, NSMutableData);
  433. CASE(CFMutableDictionaryRef, NSMutableDictionary);
  434. CASE(CFMutableSetRef, NSMutableSet);
  435. CASE(CFMutableStringRef, NSMutableString);
  436. CASE(CFNumberRef, NSNumber);
  437. CASE(CFReadStreamRef, NSInputStream);
  438. CASE(CFRunLoopTimerRef, NSTimer);
  439. CASE(CFSetRef, NSSet);
  440. CASE(CFStringRef, NSString);
  441. CASE(CFTimeZoneRef, NSTimeZone);
  442. CASE(CFURLRef, NSURL);
  443. CASE(CFWriteStreamRef, NSOutputStream);
  444. #undef CASE
  445. return NO;
  446. }
  447. + (NSString *)editableJSONStringForObject:(id)object
  448. {
  449. NSString *editableDescription = nil;
  450. if (object) {
  451. // This is a hack to use JSON serialization for our editable objects.
  452. // NSJSONSerialization doesn't allow writing fragments - the top level object must be an array or dictionary.
  453. // We always wrap the object inside an array and then strip the outer square braces off the final string.
  454. NSArray *wrappedObject = @[object];
  455. if ([NSJSONSerialization isValidJSONObject:wrappedObject]) {
  456. NSString *wrappedDescription = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:wrappedObject options:0 error:NULL] encoding:NSUTF8StringEncoding];
  457. editableDescription = [wrappedDescription substringWithRange:NSMakeRange(1, wrappedDescription.length - 2)];
  458. }
  459. }
  460. return editableDescription;
  461. }
  462. + (id)objectValueFromEditableJSONString:(NSString *)string
  463. {
  464. id value = nil;
  465. // nil for empty string/whitespace
  466. if ([[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0) {
  467. value = [NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:NULL];
  468. }
  469. return value;
  470. }
  471. + (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString
  472. {
  473. NSNumberFormatter *formatter = [NSNumberFormatter new];
  474. [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
  475. NSNumber *number = [formatter numberFromString:inputString];
  476. // Make sure we box the number with the correct type encoding so it can be properly unboxed later via getValue:
  477. NSValue *value = nil;
  478. if (strcmp(typeEncoding, @encode(char)) == 0) {
  479. char primitiveValue = [number charValue];
  480. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  481. } else if (strcmp(typeEncoding, @encode(int)) == 0) {
  482. int primitiveValue = [number intValue];
  483. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  484. } else if (strcmp(typeEncoding, @encode(short)) == 0) {
  485. short primitiveValue = [number shortValue];
  486. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  487. } else if (strcmp(typeEncoding, @encode(long)) == 0) {
  488. long primitiveValue = [number longValue];
  489. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  490. } else if (strcmp(typeEncoding, @encode(long long)) == 0) {
  491. long long primitiveValue = [number longLongValue];
  492. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  493. } else if (strcmp(typeEncoding, @encode(unsigned char)) == 0) {
  494. unsigned char primitiveValue = [number unsignedCharValue];
  495. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  496. } else if (strcmp(typeEncoding, @encode(unsigned int)) == 0) {
  497. unsigned int primitiveValue = [number unsignedIntValue];
  498. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  499. } else if (strcmp(typeEncoding, @encode(unsigned short)) == 0) {
  500. unsigned short primitiveValue = [number unsignedShortValue];
  501. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  502. } else if (strcmp(typeEncoding, @encode(unsigned long)) == 0) {
  503. unsigned long primitiveValue = [number unsignedLongValue];
  504. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  505. } else if (strcmp(typeEncoding, @encode(unsigned long long)) == 0) {
  506. unsigned long long primitiveValue = [number unsignedLongValue];
  507. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  508. } else if (strcmp(typeEncoding, @encode(float)) == 0) {
  509. float primitiveValue = [number floatValue];
  510. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  511. } else if (strcmp(typeEncoding, @encode(double)) == 0) {
  512. double primitiveValue = [number doubleValue];
  513. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  514. }
  515. return value;
  516. }
  517. + (void)enumerateTypesInStructEncoding:(const char *)structEncoding usingBlock:(void (^)(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset))typeBlock
  518. {
  519. if (structEncoding && structEncoding[0] == FLEXTypeEncodingStructBegin) {
  520. const char *equals = strchr(structEncoding, '=');
  521. if (equals) {
  522. const char *nameStart = structEncoding + 1;
  523. NSString *structName = [@(structEncoding) substringWithRange:NSMakeRange(nameStart - structEncoding, equals - nameStart)];
  524. NSUInteger fieldAlignment = 0;
  525. NSUInteger structSize = 0;
  526. @try {
  527. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  528. NSGetSizeAndAlignment(structEncoding, &structSize, &fieldAlignment);
  529. } @catch (NSException *exception) { }
  530. if (structSize > 0) {
  531. NSUInteger runningFieldIndex = 0;
  532. NSUInteger runningFieldOffset = 0;
  533. const char *typeStart = equals + 1;
  534. while (*typeStart != FLEXTypeEncodingStructEnd) {
  535. NSUInteger fieldSize = 0;
  536. // If the struct type encoding was successfully handled by NSGetSizeAndAlignment above, we *should* be ok with the field here.
  537. const char *nextTypeStart = NSGetSizeAndAlignment(typeStart, &fieldSize, NULL);
  538. NSString *typeEncoding = [@(structEncoding) substringWithRange:NSMakeRange(typeStart - structEncoding, nextTypeStart - typeStart)];
  539. // Padding to keep proper alignment. __attribute((packed)) structs will break here.
  540. // The type encoding is no different for packed structs, so it's not clear there's anything we can do for those.
  541. const NSUInteger currentSizeSum = runningFieldOffset % fieldAlignment;
  542. if (currentSizeSum != 0 && currentSizeSum + fieldSize > fieldAlignment) {
  543. runningFieldOffset += fieldAlignment - currentSizeSum;
  544. }
  545. typeBlock(structName, typeEncoding.UTF8String, [self readableTypeForEncoding:typeEncoding], runningFieldIndex, runningFieldOffset);
  546. runningFieldOffset += fieldSize;
  547. runningFieldIndex++;
  548. typeStart = nextTypeStart;
  549. }
  550. }
  551. }
  552. }
  553. }
  554. #pragma mark - Internal Helpers
  555. + (NSDictionary<NSString *, NSString *> *)attributesDictionaryForProperty:(objc_property_t)property
  556. {
  557. NSString *attributes = @(property_getAttributes(property));
  558. // Thanks to MAObjcRuntime for inspiration here.
  559. NSArray<NSString *> *attributePairs = [attributes componentsSeparatedByString:@","];
  560. NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary dictionaryWithCapacity:attributePairs.count];
  561. for (NSString *attributePair in attributePairs) {
  562. [attributesDictionary setObject:[attributePair substringFromIndex:1] forKey:[attributePair substringToIndex:1]];
  563. }
  564. return attributesDictionary;
  565. }
  566. + (NSString *)appendName:(NSString *)name toType:(NSString *)type
  567. {
  568. NSString *combined = nil;
  569. if ([type characterAtIndex:type.length - 1] == FLEXTypeEncodingCString) {
  570. combined = [type stringByAppendingString:name];
  571. } else {
  572. combined = [type stringByAppendingFormat:@" %@", name];
  573. }
  574. return combined;
  575. }
  576. + (NSString *)readableTypeForEncoding:(NSString *)encodingString
  577. {
  578. if (!encodingString) {
  579. return nil;
  580. }
  581. // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
  582. // class-dump has a much nicer and much more complete implementation for this task, but it is distributed under GPLv2 :/
  583. // See https://github.com/nygard/class-dump/blob/master/Source/CDType.m
  584. // Warning: this method uses multiple middle returns and macros to cut down on boilerplate.
  585. // The use of macros here was inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
  586. const char *encodingCString = encodingString.UTF8String;
  587. // Objects
  588. if (encodingCString[0] == FLEXTypeEncodingObjcObject) {
  589. NSString *class = [encodingString substringFromIndex:1];
  590. class = [class stringByReplacingOccurrencesOfString:@"\"" withString:@""];
  591. if (class.length == 0 || (class.length == 1 && [class characterAtIndex:0] == FLEXTypeEncodingUnknown)) {
  592. class = @"id";
  593. } else {
  594. class = [class stringByAppendingString:@" *"];
  595. }
  596. return class;
  597. }
  598. // C Types
  599. #define TRANSLATE(ctype) \
  600. if (strcmp(encodingCString, @encode(ctype)) == 0) { \
  601. return (NSString *)CFSTR(#ctype); \
  602. }
  603. // Order matters here since some of the cocoa types are typedefed to c types.
  604. // We can't recover the exact mapping, but we choose to prefer the cocoa types.
  605. // This is not an exhaustive list, but it covers the most common types
  606. TRANSLATE(CGRect);
  607. TRANSLATE(CGPoint);
  608. TRANSLATE(CGSize);
  609. TRANSLATE(UIEdgeInsets);
  610. TRANSLATE(UIOffset);
  611. TRANSLATE(NSRange);
  612. TRANSLATE(CGAffineTransform);
  613. TRANSLATE(CATransform3D);
  614. TRANSLATE(CGColorRef);
  615. TRANSLATE(CGPathRef);
  616. TRANSLATE(CGContextRef);
  617. TRANSLATE(NSInteger);
  618. TRANSLATE(NSUInteger);
  619. TRANSLATE(CGFloat);
  620. TRANSLATE(BOOL);
  621. TRANSLATE(int);
  622. TRANSLATE(short);
  623. TRANSLATE(long);
  624. TRANSLATE(long long);
  625. TRANSLATE(unsigned char);
  626. TRANSLATE(unsigned int);
  627. TRANSLATE(unsigned short);
  628. TRANSLATE(unsigned long);
  629. TRANSLATE(unsigned long long);
  630. TRANSLATE(float);
  631. TRANSLATE(double);
  632. TRANSLATE(long double);
  633. TRANSLATE(char *);
  634. TRANSLATE(Class);
  635. TRANSLATE(objc_property_t);
  636. TRANSLATE(Ivar);
  637. TRANSLATE(Method);
  638. TRANSLATE(Category);
  639. TRANSLATE(NSZone *);
  640. TRANSLATE(SEL);
  641. TRANSLATE(void);
  642. #undef TRANSLATE
  643. // Qualifier Prefixes
  644. // Do this after the checks above since some of the direct translations (i.e. Method) contain a prefix.
  645. #define RECURSIVE_TRANSLATE(prefix, formatString) \
  646. if (encodingCString[0] == prefix) { \
  647. NSString *recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:1]]; \
  648. return [NSString stringWithFormat:formatString, recursiveType]; \
  649. }
  650. // If there's a qualifier prefix on the encoding, translate it and then
  651. // recursively call this method with the rest of the encoding string.
  652. RECURSIVE_TRANSLATE('^', @"%@ *");
  653. RECURSIVE_TRANSLATE('r', @"const %@");
  654. RECURSIVE_TRANSLATE('n', @"in %@");
  655. RECURSIVE_TRANSLATE('N', @"inout %@");
  656. RECURSIVE_TRANSLATE('o', @"out %@");
  657. RECURSIVE_TRANSLATE('O', @"bycopy %@");
  658. RECURSIVE_TRANSLATE('R', @"byref %@");
  659. RECURSIVE_TRANSLATE('V', @"oneway %@");
  660. RECURSIVE_TRANSLATE('b', @"bitfield(%@)");
  661. #undef RECURSIVE_TRANSLATE
  662. // If we couldn't translate, just return the original encoding string
  663. return encodingString;
  664. }
  665. + (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type
  666. {
  667. // CASE macro inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
  668. #define CASE(ctype, selectorpart) \
  669. if (strcmp(type, @encode(ctype)) == 0) { \
  670. return [NSNumber numberWith ## selectorpart: *(ctype *)pointer]; \
  671. }
  672. CASE(BOOL, Bool);
  673. CASE(unsigned char, UnsignedChar);
  674. CASE(short, Short);
  675. CASE(unsigned short, UnsignedShort);
  676. CASE(int, Int);
  677. CASE(unsigned int, UnsignedInt);
  678. CASE(long, Long);
  679. CASE(unsigned long, UnsignedLong);
  680. CASE(long long, LongLong);
  681. CASE(unsigned long long, UnsignedLongLong);
  682. CASE(float, Float);
  683. CASE(double, Double);
  684. #undef CASE
  685. NSValue *value = nil;
  686. @try {
  687. value = [NSValue valueWithBytes:pointer objCType:type];
  688. } @catch (NSException *exception) {
  689. // Certain type encodings are not supported by valueWithBytes:objCType:. Just fail silently if an exception is thrown.
  690. }
  691. return value;
  692. }
  693. @end