2013-12-10 2 views
2

때때로 셀렉터 호출을 관찰하여 호출에 대한 사전 프로세스 또는 사후 프로세스를 자동으로 수행 할 수 있습니다.셀렉터 호출을 관찰하려면 어떻게해야합니까?

대상이 관측에서 사용자 지정 클래스 인 경우이 작업은 어렵지 않습니다. 이 목표를 달성하기위한 serval 접근 방식이 있습니다. 그러나 어떤 수업에도 적용하기가 어렵습니다. 예를 들어, [NSViewController -initWithNibName을 : 번들 :] 관찰 [NSObject의는 -init] 내가해야 할 노력은 무엇

은 같은 것입니다 :

무효 관찰 (클래스 clazz에, SEL 영업 이익, isClassMethod, 콜백 사전 , 콜백 (CALLBACK) 게시물); 이 작업을 수행하기 위해

, 나는 정의해야

ID replacedMethod (ID 대상, SEL 영업 이익, ...);

void replacedMethod2 (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]; 
... 

그 후

는, I는 상기 'replacedMethod'기능 중 하나는 클래스의 원래 구현 교체 'class_replaceMethod'기능을 사용한다.

이러한 'replacedMethod'함수 안에 begin과 end에서 콜백을 호출합니다. 이론적으로, 나는 목표를 달성하기 위해 원래 구현을 검색하고 '사전'과 '게시'사이에 넣기 만하면됩니다. 그러나 그것은 그것이 해결할 수없는 어려운 부분입니다. 원래 구현이 고정되어 있지 않기 때문에 서명이 어렵습니다.

다른 gunc (id, SEL, ...)의 측면에서 func (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 respondsToSelector :] 등의 메소드를 오버라이드 할 수 있기 때문에 런타임시 프록시 객체는 다른 클래스와 마찬가지로 거의 전달 될 수 있습니다.

그런 다음 메서드 호출 전후에 원하는 모든 작업을 수행 할 수 있습니다.

+0

답장을 보내 주셔서 감사를 처리하기 위해 좀 더 많은 에이전트 기능이 필요합니다. 나는 NSProxy가 최종 목표에 대한 나의 해결책이 될 수 있다고 믿습니다. 그러나 NSProxy는 'class_replaceMethod'에 비해 훨씬 더 성능에 영향을 미칠 것입니다. NSProxy 메서드는 observe 클래스의 메서드를 관찰하기 때문에 replace 메서드는 대체 된 메서드에만 영향을줍니다. 어쨌든 내가 문제를 해결할 수 없다면 NSProxy 접근법을 사용할 수 있습니다. 다시 한번 감사드립니다. –

+0

'forwardingTargetForSelector :'를 구현하면 훨씬 낮은 오버 헤드로 오버라이드하지 않는 메소드를 전달할 수 있습니다. 파견 방법에 대한 유용한 토론은 http://www.mikeash.com/pyblog/friday-qa-2009-03-27-objective-c-message-forwarding.html을 참조하십시오. –

0

Nathro Day에 감사드립니다. 누가 NSProxy를 사용했는지 아이디어를주었습니다. 성능에 미치는 영향을 심각하게 고려할 필요가 없다면 내 문제에 대한 깨끗한 해결책입니다. 내 솔루션으로 NSProxy를 사용하지 않았습니다. 성능이 내 프로젝트에서 심각하게 고려되기 때문입니다.

C 언어로 서명을 알지 못하고 다른 기능으로 호출을 전달합니다. 실제로 함수를 실제로 호출하는 방법은 없습니다. 왜냐하면 그 경우에는 서명을 모릅니다.이 경우 인수를 전달할 위치와 방법을 알 수 없기 때문입니다.

다른 cpu 아키텍처와 컴파일러는 다른 호출 규칙을 가지고있는 것으로 나타났습니다. 인수가 순서대로 하나씩 저장되므로 32 비트 CPU에서 gcc 컴파일러를 처리하는 것이 더 쉽습니다. 타입, 순서 및 기본 주소를 알고 나면이를 읽고 전달할 수 있습니다.

그러나 gcc for 64bit cpu는 인수를 복잡한 방식으로 저장합니다. 정수, 흐름 점 및 오버플로 된 인수는 메모리의 3 가지 다른 세그먼트에 저장됩니다. 구조체 인수는 다른 상황에서 세 세그먼트 모두에 저장 될 수 있습니다. 유형, 질서 및 기본 주소 만 아는 것만으로는 논쟁의 주소를 알 수 없을뿐만 아니라 대회의 정확한 규칙이 필요합니다. 나는 대회 규칙을 발견했다. 그러나, 내가 찾은 것이 실제로 컴파일러가하는 것임을 증명할 문서는 없습니다. 게다가, '@ 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 CPU 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); 
} 
관련 문제