You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

946 lines
38 KiB

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