Спасибо за Nathan Day, который дал мне идею использования NSProxy. Это чистое решение моей проблемы, если влияние на производительность не нужно рассматривать серьезно. Я не принимал NSProxy в качестве своего решения, потому что производительность в моем проекте серьезно рассматривается.
Пересылка вызова другой функции, не зная ее подписи, затруднена на языке c. На самом деле, нет способа вызвать функцию real, не знаю ее подписи, потому что в этом случае мы не сможем узнать, где читать и как передавать аргументы.
Я обнаружил, что разные архитектуры процессора и компиляторы имеют разные соглашения о вызовах. gcc-компилятор на 32-битном процессоре легче обрабатывать, поскольку аргументы хранятся поочередно по порядку. Вы можете прочитать их и передать их, как только узнаете их типы, порядок и основной адрес.
Однако gcc для 64-битного процессора хранит аргументы сложным способом. целые числа, точки потока и переполненные аргументы хранятся в трех разных сегментах памяти. аргументы structs могут храниться во всех трех сегментах в разных ситуациях. Только узнать типы, порядок и базовый адрес будет недостаточно, чтобы узнать адрес аргументов, но также необходимо точное правило конвенции. Я нашел правила конвенции. Однако нет документа, подтверждающего, что то, что я нашел, на самом деле является тем, что делает компилятор. Более того, для этого требуется описание метода времени выполнения, например: '@ 32 @ 0: 8 @ 16 {? = Id} 24', что нелегко разобрать.
Если есть подход, который может рассказать о точном смещении аргументов, тогда моя проблема может быть решена. Существует функция 'method_getArgumentInfo', похоже, что я искал. Тем не менее, он недоступен в OBJC2
К счастью, наконец я обнаружил, что описание отладки класса NSMethodSignature может помочь мне выяснить, где находится аргумент, предоставляя описание строки, которое отмечает смещения аргументов:
number of arguments = 3
frame size = 248
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (@) '@'
flags {isObject}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 0: -------- -------- -------- --------
type encoding (@) '@'
flags {isObject}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (:) ':'
flags {}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 2: -------- -------- -------- --------
type encoding ({) '{?=[3q]}'
flags {isStruct}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 24}
type encoding ([) '[3q]'
flags {isArray}
modifiers {}
frame {offset = 224, offset adjust = 0, size = 24, size adjust = 0}
memory {offset = 0, size = 24}
type encoding (q) 'q'
flags {isSigned}
modifiers {}
frame {offset = 224, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
type encoding (q) 'q'
flags {isSigned}
modifiers {}
frame {offset = 232, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 8, size = 8}
type encoding (q) 'q'
flags {isSigned}
modifiers {}
frame {offset = 240, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 16, size = 8}
и усиливающий из NSInvocation и class_replaceMethod я могу, наконец, найти решение. Слабость в том, что нет никакой гарантии, что описание NSMethodSignature всегда будет описывать точную информацию.
Следующий сегмент кода представляет собой детальную реализацию решения. ПРИМЕЧАНИЕ 1. Это не полная реализация, а просто демонстрирует идею этого. 2. этого код протестирован только с x64 процессором 3. для того, чтобы справиться с любым селектором, то нужно еще несколько функций агента для обработки различных типов возвращаемого
#import "WFObserveMessageException.h"
#import <objc/runtime.h>
@interface WFImplementationInfo : NSObject{
@package
Class _class;
IMP _func;
Method _method;
BOOL _isClassMethod;
NSInvocation *_invoke;
int _countArg;
int *_argOffsets;
_wf_message_observer_t _observer;
}
@end
@implementation WFImplementationInfo
-(void)dealloc{
if (_argOffsets) {
free(_argOffsets);
_argOffsets = NULL;
}
}
@end
NSMutableDictionary *getFunctionDict(){
static NSMutableDictionary *dict;
if(!dict){
dict = [NSMutableDictionary dictionary];
}
return dict;
}
NSString *keyForClassAndOp(NSString *className, NSString *opName, BOOL isClassMethod){
return [NSString stringWithFormat:@"%c[%@ %@]", (isClassMethod ? '+' : '-'), className, opName];
}
WFImplementationInfo *prepareInvocation(id obj, SEL op, va_list ap){
Class clazz;
WFImplementationInfo *func;
BOOL isMetaClass;
NSString *key;
NSDictionary *dict;
clazz = [obj class];
dict = getFunctionDict();
//search and see if the this class or its parent classes is observed, there must at lest one of them is observed
while (clazz) {
isMetaClass = class_isMetaClass(clazz);
key = keyForClassAndOp(NSStringFromClass(clazz), NSStringFromSelector(op), isMetaClass);
func = [dict objectForKey:key];
if (func) {
break;
}
clazz = class_getSuperclass(clazz);
}
func->_invoke.target = obj;
for (int i = 2; i < func->_countArg; i++) {
//set up the arguments of the invocation.
[func->_invoke setArgument:ap->reg_save_area + func->_argOffsets[i] atIndex:i];
}
return func;
}
id agent(id obj, SEL op, ...){
va_list ap;
WFImplementationInfo *func;
id retObj;
//the va_list could tell where is the base of the arguments
va_start(ap, op);
func = prepareInvocation(obj, op, ap);
va_end(ap);
class_replaceMethod(func->_class, op, func->_func, nil);
func->_observer(func->_invoke, &retObj);
class_replaceMethod(func->_class, op, (IMP)agent, nil);
return retObj;
}
void wf_observe_message(NSString *className, NSString *opName, _wf_message_observer_t observer){
WFImplementationInfo *func;
NSMethodSignature *signature;
NSScanner *scanner;
NSString *desc, *key;
SEL op;
int sign, count;
func = [[WFImplementationInfo alloc] init];
func->_observer = observer;
func->_class = NSClassFromString(className);
sign = [opName characterAtIndex:0];
opName = [opName substringFromIndex:1];
op = NSSelectorFromString(opName);
switch (sign) {
case '-':
func->_isClassMethod = NO;
func->_method = class_getInstanceMethod(func->_class, op);
signature = [func->_class instanceMethodSignatureForSelector:op];
break;
case '+':
func->_isClassMethod = YES;
func->_method = class_getClassMethod(func->_class, op);
signature = [func->_class methodSignatureForSelector:op];
break;
default:
WFThrow WFObserveMessageExceptionA(@"Selector name MUST start with '-' or '+' for indicating whether it is a instance method");
break;
}
key = keyForClassAndOp(className, opName, func->_isClassMethod);
func->_func = method_getImplementation(func->_method);
func->_countArg = method_getNumberOfArguments(func->_method);
func->_argOffsets = malloc((func->_countArg) * sizeof(int));
func->_invoke = [NSInvocation invocationWithMethodSignature:signature];
func->_invoke.selector = op;
func->_argOffsets[0] = 0; //offset of id
func->_argOffsets[1] = 8; //offset of SEL
count = 2;
desc = [signature debugDescription];
scanner = [NSScanner scannerWithString:desc];
[scanner scanUpToString:@"argument 2" intoString:nil];
//scan the offsets of the arguments
while (!scanner.isAtEnd) {
[scanner scanUpToString:@"offset = " intoString:nil];
scanner.scanLocation = scanner.scanLocation + 9;
[scanner scanInt:&func->_argOffsets[count]];
if(func->_argOffsets[count] == 0){ //if the offset is 0, that means the argument is a struct, the offset of the struct is the offset of the its first member
[scanner scanUpToString:@"offset = " intoString:nil];
scanner.scanLocation = scanner.scanLocation + 9;
[scanner scanInt:&func->_argOffsets[count]];
}
[scanner scanUpToString:@"argument" intoString:nil];
count++;
}
[getFunctionDict() setObject:func forKey:key];
//check if the method is valid
if (!func->_method) {
WFThrow WFObserveMessageExceptionA(@"Class has no selector '%@' for class '%@'", opName, className);
}
class_replaceMethod(func->_class, op, (IMP)agent, nil);
}
благодарит за Ваш ответ. Я считаю, что NSProxy может быть моим решением конечной цели. однако, я думаю, что NSProxy повлияет на производительность намного больше, чем «class_replaceMethod». Это связано с тем, что метод NSProxy будет наблюдать любой метод класса наблюдения, но подход замены только влияет на замену. Во всяком случае, я могу использовать подход NSProxy, если я не могу решить проблему. Еще раз спасибо. –
Если вы реализуете 'forwardingTargetForSelector:', это позволит вам перенаправить методы, которые вы не переопределяете, с гораздо меньшими накладными расходами. См. Http://www.mikeash.com/pyblog/friday-qa-2009-03-27-objective-c-message-forwarding.html для некоторого полезного обсуждения того, как работает диспетчеризация. –