Да, это действительно возможно, но это решение является ABI-специфичным (не гарантируется для работы на всех платформах) и широко использует информацию, доступную во время работы о методах.
Мы должны сначала получить информацию о методе, который мы обмениваем блоком. Это делается с помощью NSMethodSignature
, который содержит такую информацию, как:
- Количество аргументов
- Размер (в байтах) каждого аргумента
- Размер возвратного типа
Это позволяет обернуть (почти) любого метода без специального кода для этого метода, тем самым создавая повторно используемую функцию.
Во-вторых, нам нужен способ безопасно отправлять вызовы методов во время выполнения. Мы делаем это через NSInvocation
, что дает нам возможность создавать динамический и безопасный вызов метода во время выполнения.
В-третьих, нам нужно иметь блок, который может принимать любое количество аргументов, переданных и отправку. Это делается с помощью API-интерфейсов C va_list
и должно работать на 99% методов.
Наконец, нам нужно получить возвращаемое значение и вернуть его из нашего блока. Это часть всей операции, которая может не работать, из-за странности с возвращающимися структурами и т. Д. С помощью среды выполнения Objective-C.
Однако, если вы придерживаетесь примитивных типов и объектов Objective-C, этот код должен отлично работать для вас.
Несколько вещей, чтобы отметить о такой реализации:
Это зависит от неопределенного поведения с отливкой блока & типов функций, однако, из-за соглашение о вызовах прошивки и Mac, это не должно возникают проблемы (если у вашего метода нет другого типа возврата, кроме того, что ожидает блок).
Он также полагается на неопределенное поведение с результатом вызова va_arg
с типом, который может быть не тем, что передается, однако, поскольку типы имеют одинаковый размер, это никогда не должно быть проблемой.
Без дальнейших церемоний, вот пример кода, с последующим осуществлением:
@interface MyObj : NSObject
-(void) doSomething;
@end
@implementation MyObj
-(void) doSomething
{
NSLog(@"This is me, doing something! %p", self);
}
-(id) doSomethingWithArgs:(long) arg :(short) arg2{
return [NSString stringWithFormat:@"%ld %d", arg, arg2];
}
@end
int main() {
// try out our selector wrapping
MyObj *obj = [MyObj new];
id (^asBlock)(long, short) = createBlock(obj, @selector(doSomethingWithArgs::));
NSLog(@"%@", asBlock(123456789, 456));
}
/* WARNING, ABI SPECIFIC, BLAH BLAH BLAH NOT PORTABLE! */
static inline void getArgFromListOfSize(va_list *args, void *first, size_t size, size_t align, void *dst, BOOL isFirst) {
// create a map of sizes to types
switch (size) {
// varargs are weird, and are aligned to 32 bit boundaries. We still only copy the size needed, though.
// these cases should cover all 32 bit pointers (iOS), boolean values, and floats too.
case sizeof(uint8_t): {
uint8_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t);
memcpy(dst, &tmp, size);
break;
}
case sizeof(uint16_t): {
uint16_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t);
memcpy(dst, &tmp, size);
break;
}
case sizeof(uint32_t): {
uint32_t tmp = isFirst ? (uint32_t) first : va_arg(*args, uint32_t);
memcpy(dst, &tmp, size);
break;
}
// this should cover 64 bit pointers (Mac), and longs, and doubles
case sizeof(uint64_t): {
uint64_t tmp = isFirst ? (uint64_t) first : va_arg(*args, uint64_t);
memcpy(dst, &tmp, size);
break;
}
/* This has to be commented out to work on iOS (as CGSizes are 64 bits)
// common 'other' types (covers CGSize, CGPoint)
case sizeof(CGPoint): {
CGPoint tmp = isFirst ? *(CGPoint *) &first : va_arg(*args, CGPoint);
memcpy(dst, &tmp, size);
break;
}
*/
// CGRects are fairly common on iOS, so we'll include those as well
case sizeof(CGRect): {
CGRect tmp = isFirst ? *(CGRect *) &first : va_arg(*args, CGRect);
memcpy(dst, &tmp, size);
break;
}
default: {
fprintf(stderr, "WARNING! Could not bind parameter of size %zu, unkown type! Going to have problems down the road!", size);
break;
}
}
}
id createBlock(id self, SEL _cmd) {
NSMethodSignature *methodSig = [self methodSignatureForSelector:_cmd];
if (methodSig == nil)
return nil;
return ^(void *arg, ...) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setTarget:self];
[invocation setSelector:_cmd];
NSUInteger argc = [methodSig numberOfArguments];
va_list args;
va_start(args, arg);
for (int argi = 2; argi < argc; argi++) {
const char *type = [methodSig getArgumentTypeAtIndex:argi];
NSUInteger size;
NSUInteger align;
// get the size
NSGetSizeAndAlignment(type, &size, &align);
// find the right type
void *argument = alloca(size);
getArgFromListOfSize(&args, arg, size, align, argument, argi == 2);
[invocation setArgument:argument atIndex:argi];
}
va_end(args);
[invocation invoke];
// get the return value
if (methodSig.methodReturnLength != 0) {
void *retVal = alloca(methodSig.methodReturnLength);
[invocation getReturnValue:retVal];
return *((void **) retVal);
}
return nil;
};
}
Позвольте мне знать, если у вас есть какие-либо проблемы с эта реализация!
Просто заметьте, кто читает это, кто может быть смущен: я ответил на свой вопрос, так как сам понял это. –
Вы проработали его в нулевые минуты, неплохая работа! – Wain
@Wain трудно. Принял у меня около 2 дней солидной работы, чтобы получить это. –