A2BlockInvocation.m 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. //
  2. // A2BlockInvocation.m
  3. // BlocksKit
  4. //
  5. #import "A2BlockInvocation.h"
  6. NSString *const A2IncompatibleMethodSignatureKey = @"incompatibleMethodSignature";
  7. #ifndef NSFoundationVersionNumber10_8
  8. #define NSFoundationVersionNumber10_8 945.00
  9. #endif
  10. #ifndef NSFoundationVersionNumber_iOS_6_0
  11. #define NSFoundationVersionNumber_iOS_6_0 993.00
  12. #endif
  13. #pragma mark Block Internals
  14. typedef NS_OPTIONS(int, BKBlockFlags) {
  15. BKBlockFlagsHasCopyDisposeHelpers = (1 << 25),
  16. BKBlockFlagsHasSignature = (1 << 30)
  17. };
  18. typedef struct _BKBlock {
  19. __unused Class isa;
  20. BKBlockFlags flags;
  21. __unused int reserved;
  22. void (__unused *invoke)(struct _BKBlock *block, ...);
  23. struct {
  24. unsigned long int reserved;
  25. unsigned long int size;
  26. // requires BKBlockFlagsHasCopyDisposeHelpers
  27. void (*copy)(void *dst, const void *src);
  28. void (*dispose)(const void *);
  29. // requires BKBlockFlagsHasSignature
  30. const char *signature;
  31. const char *layout;
  32. } *descriptor;
  33. // imported variables
  34. } *BKBlockRef;
  35. @interface A2BlockInvocation ()
  36. @property (nonatomic, readonly) NSMethodSignature *blockSignature;
  37. @end
  38. @implementation A2BlockInvocation
  39. /** Determines if two given signatures (block or method) are compatible.
  40. A signature is compatible with another signature if their return types and
  41. parameter types are equal, minus the parameter types dedicated to the Obj-C
  42. method reciever and selector or a block literal argument.
  43. @param signatureA Any signature object reflecting a block or method signature
  44. @param signatureB Any signature object reflecting a block or method signature
  45. @return YES if the given signatures may be used to dispatch for one another
  46. */
  47. + (BOOL)isSignature:(NSMethodSignature *)signatureA compatibleWithSignature:(NSMethodSignature *)signatureB __attribute__((pure))
  48. {
  49. if (!signatureA || !signatureB) return NO;
  50. if ([signatureA isEqual:signatureB]) return YES;
  51. if (signatureA.methodReturnType[0] != signatureB.methodReturnType[0]) return NO;
  52. NSMethodSignature *methodSignature = nil, *blockSignature = nil;
  53. if (signatureA.numberOfArguments > signatureB.numberOfArguments) {
  54. methodSignature = signatureA;
  55. blockSignature = signatureB;
  56. } else if (signatureB.numberOfArguments > signatureA.numberOfArguments) {
  57. methodSignature = signatureB;
  58. blockSignature = signatureA;
  59. } else {
  60. return NO;
  61. }
  62. NSUInteger numberOfArguments = methodSignature.numberOfArguments;
  63. for (NSUInteger i = 2; i < numberOfArguments; i++) {
  64. if ([methodSignature getArgumentTypeAtIndex:i][0] != [blockSignature getArgumentTypeAtIndex:i - 1][0])
  65. return NO;
  66. }
  67. return YES;
  68. }
  69. /** Inspects the given block literal and returns a compatible type signature.
  70. Unlike a typical method signature, a block type signature has no `self` (`'@'`)
  71. or `_cmd` (`':'`) parameter, but instead just one parameter for the block itself
  72. (`'@?'`).
  73. @param block An Objective-C block literal
  74. @return A method signature matching the declared prototype for the block
  75. */
  76. + (NSMethodSignature *)typeSignatureForBlock:(id)block __attribute__((pure, nonnull(1)))
  77. {
  78. BKBlockRef layout = (__bridge void *)block;
  79. if (!(layout->flags & BKBlockFlagsHasSignature))
  80. return nil;
  81. void *desc = layout->descriptor;
  82. desc += 2 * sizeof(unsigned long int);
  83. if (layout->flags & BKBlockFlagsHasCopyDisposeHelpers)
  84. desc += 2 * sizeof(void *);
  85. if (!desc)
  86. return nil;
  87. const char *signature = (*(const char **)desc);
  88. return [NSMethodSignature signatureWithObjCTypes:signature];
  89. }
  90. /// Creates a method signature compatible with a given block signature.
  91. + (NSMethodSignature *)methodSignatureForBlockSignature:(NSMethodSignature *)original
  92. {
  93. if (!original) return nil;
  94. if (original.numberOfArguments < 1) {
  95. return nil;
  96. }
  97. if (original.numberOfArguments >= 2 && strcmp(@encode(SEL), [original getArgumentTypeAtIndex:1]) == 0) {
  98. return original;
  99. }
  100. // initial capacity is num. arguments - 1 (@? -> @) + 1 (:) + 1 (ret type)
  101. // optimistically assuming most signature components are char[1]
  102. NSMutableString *signature = [[NSMutableString alloc] initWithCapacity:original.numberOfArguments + 1];
  103. const char *retTypeStr = original.methodReturnType;
  104. [signature appendFormat:@"%s%s%s", retTypeStr, @encode(id), @encode(SEL)];
  105. for (NSUInteger i = 1; i < original.numberOfArguments; i++) {
  106. const char *typeStr = [original getArgumentTypeAtIndex:i];
  107. NSString *type = [[NSString alloc] initWithBytesNoCopy:(void *)typeStr length:strlen(typeStr) encoding:NSUTF8StringEncoding freeWhenDone:NO];
  108. [signature appendString:type];
  109. }
  110. return [NSMethodSignature signatureWithObjCTypes:signature.UTF8String];
  111. }
  112. + (NSMethodSignature *)methodSignatureForBlock:(id)block
  113. {
  114. NSMethodSignature *original = [self typeSignatureForBlock:block];
  115. if (!original) return nil;
  116. return [self methodSignatureForBlockSignature:original];
  117. }
  118. - (instancetype)initWithBlock:(id)block methodSignature:(NSMethodSignature *)methodSignature blockSignature:(NSMethodSignature *)blockSignature
  119. {
  120. self = [super init];
  121. if (self) {
  122. _block = [block copy];
  123. _methodSignature = methodSignature;
  124. _blockSignature = blockSignature;
  125. }
  126. return self;
  127. }
  128. - (instancetype)initWithBlock:(id)block
  129. {
  130. NSParameterAssert(block);
  131. NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
  132. NSMethodSignature *methodSignature = [[self class] methodSignatureForBlockSignature:blockSignature];
  133. NSAssert(methodSignature, @"Incompatible block: %@", block);
  134. return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]);
  135. }
  136. - (instancetype)initWithBlock:(id)block methodSignature:(NSMethodSignature *)methodSignature
  137. {
  138. NSParameterAssert(block);
  139. NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
  140. if (![[self class] isSignature:methodSignature compatibleWithSignature:blockSignature]) {
  141. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Attempted to create block invocation with incompatible signatures" userInfo:@{A2IncompatibleMethodSignatureKey: methodSignature}];
  142. }
  143. return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]);
  144. }
  145. - (BOOL)invokeWithInvocation:(NSInvocation *)outerInv returnValue:(out NSValue **)outReturnValue setOnInvocation:(BOOL)setOnInvocation
  146. {
  147. NSParameterAssert(outerInv);
  148. NSMethodSignature *sig = self.methodSignature;
  149. if (![outerInv.methodSignature isEqual:sig]) {
  150. NSAssert(0, @"Attempted to invoke block invocation with incompatible frame");
  151. return NO;
  152. }
  153. NSInvocation *innerInv = [NSInvocation invocationWithMethodSignature:self.blockSignature];
  154. void *argBuf = NULL;
  155. for (NSUInteger i = 2; i < sig.numberOfArguments; i++) {
  156. const char *type = [sig getArgumentTypeAtIndex:i];
  157. NSUInteger argSize;
  158. NSGetSizeAndAlignment(type, &argSize, NULL);
  159. if (!(argBuf = reallocf(argBuf, argSize))) {
  160. return NO;
  161. }
  162. [outerInv getArgument:argBuf atIndex:i];
  163. [innerInv setArgument:argBuf atIndex:i - 1];
  164. }
  165. [innerInv invokeWithTarget:self.block];
  166. NSUInteger retSize = sig.methodReturnLength;
  167. if (retSize) {
  168. if (outReturnValue || setOnInvocation) {
  169. if (!(argBuf = reallocf(argBuf, retSize))) {
  170. return NO;
  171. }
  172. [innerInv getReturnValue:argBuf];
  173. if (setOnInvocation) {
  174. [outerInv setReturnValue:argBuf];
  175. }
  176. if (outReturnValue) {
  177. *outReturnValue = [NSValue valueWithBytes:argBuf objCType:sig.methodReturnType];
  178. }
  179. }
  180. } else {
  181. if (outReturnValue) {
  182. *outReturnValue = nil;
  183. }
  184. }
  185. free(argBuf);
  186. return YES;
  187. }
  188. - (void)invokeWithInvocation:(NSInvocation *)inv
  189. {
  190. [self invokeWithInvocation:inv returnValue:NULL setOnInvocation:YES];
  191. }
  192. - (BOOL)invokeWithInvocation:(NSInvocation *)inv returnValue:(out NSValue **)returnValue
  193. {
  194. return [self invokeWithInvocation:inv returnValue:returnValue setOnInvocation:NO];
  195. }
  196. @end