cdts/xdts-ios 3/TreeHole/Code/Utility/ImagePicker/BlocksKit/DynamicDelegate/A2BlockInvocation.m
2023-07-27 09:20:00 +08:00

254 lines
7.8 KiB
Objective-C

//
// A2BlockInvocation.m
// BlocksKit
//
#import "A2BlockInvocation.h"
NSString *const A2IncompatibleMethodSignatureKey = @"incompatibleMethodSignature";
#pragma mark Block Internals
typedef NS_OPTIONS(int, BKBlockFlags) {
BKBlockFlagsHasCopyDisposeHelpers = (1 << 25),
BKBlockFlagsHasSignature = (1 << 30)
};
typedef struct _BKBlock {
__unused Class isa;
BKBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _BKBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires BKBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires BKBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *BKBlockRef;
NS_INLINE BOOL typesCompatible(const char *a, const char *b) {
if (a[0] == b[0]) { return YES; }
NSUInteger aSize, aAlign, bSize, bAlign;
NSGetSizeAndAlignment(a, &aSize, &aAlign);
NSGetSizeAndAlignment(a, &bSize, &bAlign);
if (aSize == bSize && aAlign == bAlign) { return YES; }
return !!strcmp(a, b);
}
@interface A2BlockInvocation ()
@property (nonatomic, readonly) NSMethodSignature *blockSignature;
@end
@implementation A2BlockInvocation
/** Determines if two given signatures (block or method) are compatible.
A signature is compatible with another signature if their return types and
parameter types are equal, minus the parameter types dedicated to the Obj-C
method reciever and selector or a block literal argument.
@param signatureA Any signature object reflecting a block or method signature
@param signatureB Any signature object reflecting a block or method signature
@return YES if the given signatures may be used to dispatch for one another
*/
+ (BOOL)isSignature:(NSMethodSignature *)signatureA compatibleWithSignature:(NSMethodSignature *)signatureB __attribute__((pure))
{
if (!signatureA || !signatureB) return NO;
if ([signatureA isEqual:signatureB]) return YES;
if (!typesCompatible(signatureA.methodReturnType, signatureB.methodReturnType)) return NO;
NSMethodSignature *methodSignature = nil, *blockSignature = nil;
if (signatureA.numberOfArguments > signatureB.numberOfArguments) {
methodSignature = signatureA;
blockSignature = signatureB;
} else if (signatureB.numberOfArguments > signatureA.numberOfArguments) {
methodSignature = signatureB;
blockSignature = signatureA;
} else {
return NO;
}
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
for (NSUInteger i = 2; i < numberOfArguments; i++) {
if (!typesCompatible([methodSignature getArgumentTypeAtIndex:i], [blockSignature getArgumentTypeAtIndex:i - 1])) {
return NO;
}
}
return YES;
}
/** Inspects the given block literal and returns a compatible type signature.
Unlike a typical method signature, a block type signature has no `self` (`'@'`)
or `_cmd` (`':'`) parameter, but instead just one parameter for the block itself
(`'@?'`).
@param block An Objective-C block literal
@return A method signature matching the declared prototype for the block
*/
+ (NSMethodSignature *)typeSignatureForBlock:(id)block __attribute__((pure, nonnull(1)))
{
BKBlockRef layout = (__bridge void *)block;
if (!(layout->flags & BKBlockFlagsHasSignature))
return nil;
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
if (layout->flags & BKBlockFlagsHasCopyDisposeHelpers)
desc += 2 * sizeof(void *);
if (!desc)
return nil;
const char *signature = (*(const char **)desc);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
/// Creates a method signature compatible with a given block signature.
+ (NSMethodSignature *)methodSignatureForBlockSignature:(NSMethodSignature *)original
{
if (!original) return nil;
if (original.numberOfArguments < 1) {
return nil;
}
if (original.numberOfArguments >= 2 && strcmp(@encode(SEL), [original getArgumentTypeAtIndex:1]) == 0) {
return original;
}
// initial capacity is num. arguments - 1 (@? -> @) + 1 (:) + 1 (ret type)
// optimistically assuming most signature components are char[1]
NSMutableString *signature = [[NSMutableString alloc] initWithCapacity:original.numberOfArguments + 1];
const char *retTypeStr = original.methodReturnType;
[signature appendFormat:@"%s%s%s", retTypeStr, @encode(id), @encode(SEL)];
for (NSUInteger i = 1; i < original.numberOfArguments; i++) {
const char *typeStr = [original getArgumentTypeAtIndex:i];
NSString *type = [[NSString alloc] initWithBytesNoCopy:(void *)typeStr length:strlen(typeStr) encoding:NSUTF8StringEncoding freeWhenDone:NO];
[signature appendString:type];
}
return [NSMethodSignature signatureWithObjCTypes:signature.UTF8String];
}
+ (NSMethodSignature *)methodSignatureForBlock:(id)block
{
NSMethodSignature *original = [self typeSignatureForBlock:block];
if (!original) return nil;
return [self methodSignatureForBlockSignature:original];
}
- (instancetype)initWithBlock:(id)block methodSignature:(NSMethodSignature *)methodSignature blockSignature:(NSMethodSignature *)blockSignature
{
self = [super init];
if (self) {
_block = [block copy];
_methodSignature = methodSignature;
_blockSignature = blockSignature;
}
return self;
}
- (instancetype)initWithBlock:(id)block
{
NSParameterAssert(block);
NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
NSMethodSignature *methodSignature = [[self class] methodSignatureForBlockSignature:blockSignature];
NSAssert(methodSignature, @"Incompatible block: %@", block);
return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]);
}
- (instancetype)initWithBlock:(id)block methodSignature:(NSMethodSignature *)methodSignature
{
NSParameterAssert(block);
NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
if (![[self class] isSignature:methodSignature compatibleWithSignature:blockSignature]) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Attempted to create block invocation with incompatible signatures" userInfo:@{A2IncompatibleMethodSignatureKey: methodSignature}];
}
return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]);
}
- (BOOL)invokeWithInvocation:(NSInvocation *)outerInv returnValue:(out NSValue **)outReturnValue setOnInvocation:(BOOL)setOnInvocation
{
NSParameterAssert(outerInv);
NSMethodSignature *sig = self.methodSignature;
if (![outerInv.methodSignature isEqual:sig]) {
NSAssert(0, @"Attempted to invoke block invocation with incompatible frame");
return NO;
}
NSInvocation *innerInv = [NSInvocation invocationWithMethodSignature:self.blockSignature];
void *argBuf = NULL;
for (NSUInteger i = 2; i < sig.numberOfArguments; i++) {
const char *type = [sig getArgumentTypeAtIndex:i];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
if (!(argBuf = reallocf(argBuf, argSize))) {
return NO;
}
[outerInv getArgument:argBuf atIndex:i];
[innerInv setArgument:argBuf atIndex:i - 1];
}
[innerInv invokeWithTarget:self.block];
NSUInteger retSize = sig.methodReturnLength;
if (retSize) {
if (outReturnValue || setOnInvocation) {
if (!(argBuf = reallocf(argBuf, retSize))) {
return NO;
}
[innerInv getReturnValue:argBuf];
if (setOnInvocation) {
[outerInv setReturnValue:argBuf];
}
if (outReturnValue) {
*outReturnValue = [NSValue valueWithBytes:argBuf objCType:sig.methodReturnType];
}
}
} else {
if (outReturnValue) {
*outReturnValue = nil;
}
}
free(argBuf);
return YES;
}
- (void)invokeWithInvocation:(NSInvocation *)inv
{
[self invokeWithInvocation:inv returnValue:NULL setOnInvocation:YES];
}
- (BOOL)invokeWithInvocation:(NSInvocation *)inv returnValue:(out NSValue **)returnValue
{
return [self invokeWithInvocation:inv returnValue:returnValue setOnInvocation:NO];
}
@end