2013-12-10 4 views
2

, мы иногда хотим наблюдать за вызовом селектора, чтобы мы могли автоматически выполнить предварительный процесс или постпроцесс для вызова.Как я могу наблюдать за вызовом селектора

Это не сложно, если цель является обычным классом в наблюдении. Для достижения этой цели существуют сервачные подходы. Однако это трудно сделать для ЛЮБОГО класса. например, наблюдать [NSViewController -initWithNibName: сверток:], [NSObject -init]

То, что я пытался сделать, это что-то вроде:

недействительным наблюдения (класс clazz, SEL оп, isClassMethod, CALLBACK пред , Сообщение CALLBACK);

для того, чтобы сделать это, мне нужно определить:

идентификатор (целевой идентификатор, SEL оп ...) replacedMethod;

void replacementMethod2 (id target, SEL op, ...);

...

, а затем внутри функции наблюдения выше, я получаю оригинальную реализацию класса и поместить его в карту.

WFFuncWrap *wrap; 
NSString *key; 
... 
wrap = [[WFFuncWrap alloc] init]; 
wrap->_isClassMethod = isClassMethod; 
if (isClassMethod) { 
    wrap->_method = class_getClassMethod(clazz, op); 
}else{ 
    wrap->_method = class_getInstanceMethod(clazz, op); 
} 
wrap->_func = method_getImplementation(wrap->_method); 
[_dict setObject:wrap forKey:key]; 
... 

и после этого, я использую функцию «class_replaceMethod» заменить оригинальную реализацию класса с одной из функций Вышеуказанной «replacedMethod».

Внутри этих функций «replaceMethod» я вызываю обратные вызовы в начале и в конце. в теории, мне нужно только искать первоначальную реализацию и поставить ее между «pre» и «post», чтобы достичь цели. Тем не менее, это сложная часть, которую я не могу решить. это сложно из-за подписи, оригинальная реализация не исправлена.

Я не нашел способ вызова func (id, SEL, ...) в сторону другого gunc (id, SEL, ...). На самом деле, я собираю код веры, чтобы решить проблему, но это то, что я не знаком. Я пытался, но это слишком сложно.

id replacedMethod(id obj, SEL op, ...){ 
    CALLBACK pre, post; 
    IMP originalImp; 
    id retObj; 

    ... 
    pre(obj, op); 
    //call original implementation, HOW ? 
    post(obj, op); 
    return retObj; 
} 

Есть ли идеи решить эту проблему? Большое спасибо!!

ответ

1

Совершенно другой способ сделать то, что вы пытаетесь сделать, поэтому я не знаю, поможет ли это, создать прокси-класс, который может перенаправлять все методы, вызванные на него, в целевой класс (посмотрите на NSProxy), вы можете использовать - [NSObject forwardInvocation:], который дает вам все аргументы, упакованные в объект NSInvocation. Во время выполнения ваш прокси-объект может передавать себя как почти любой другой класс, могут быть некоторые случаи с краем, потому что ваш прокси-класс может переопределять такие методы, как - [NSObject isKindOfClass:], - [NSObject отвечаетToSelector:] и т. Д.

Затем вы можете делать все, что захотите, до и после вызова метода.

+0

благодарит за Ваш ответ. Я считаю, что NSProxy может быть моим решением конечной цели. однако, я думаю, что NSProxy повлияет на производительность намного больше, чем «class_replaceMethod». Это связано с тем, что метод NSProxy будет наблюдать любой метод класса наблюдения, но подход замены только влияет на замену. Во всяком случае, я могу использовать подход NSProxy, если я не могу решить проблему. Еще раз спасибо. –

+0

Если вы реализуете 'forwardingTargetForSelector:', это позволит вам перенаправить методы, которые вы не переопределяете, с гораздо меньшими накладными расходами. См. Http://www.mikeash.com/pyblog/friday-qa-2009-03-27-objective-c-message-forwarding.html для некоторого полезного обсуждения того, как работает диспетчеризация. –

0

Спасибо за 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); 
}