Да, това наистина е възможно, но това решение е специфично за ABI (не е гарантирано, че работи на всички платформи) и широко използва наличната по време на изпълнение информация относно методите.
Това, което първо трябва да направим, е да получим информация за метода, който обвиваме с блока. Това става чрез NSMethodSignature
, който съдържа информация като:
- Брой аргументи
- Размер (в байтове) на всеки аргумент
- Размер на връщания тип
Това ни позволява да обгърнем (почти) всеки метод без специфичен код за този метод, като по този начин създадем функция, която може да се използва повторно.
Второ, имаме нужда от начин за безопасно изпращане на извиквания на метод по време на изпълнение. Ние правим това чрез NSInvocation
, което ни дава възможност да създадем динамично и безопасно извикване на метод по време на изпълнение.
Трето, трябва да имаме блок, който може да приема произволен брой предадени аргументи и изпращането им. Това се прави чрез va_list
API на C и трябва да работи за 99% от методите.
И накрая, трябва да получим върнатата стойност и да можем да я върнем от нашия блок. Това е частта от цялата операция, която е възможно да не работи, поради странности с връщащите се структури и такива с времето за изпълнение на Objective-C.
Въпреки това, докато се придържате към примитивни типове и Objective-C обекти, този код трябва да работи чудесно за вас.
Няколко неща, които трябва да отбележите относно тази реализация:
Зависи от недефинирано поведение с кастинг на типове блокове и функции, но поради конвенциите за извикване на iOS и 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;
};
}
Уведомете ме, ако имате проблеми с това внедряване!
person
Richard J. Ross III
schedule
27.04.2013