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);
}
답장을 보내 주셔서 감사를 처리하기 위해 좀 더 많은 에이전트 기능이 필요합니다. 나는 NSProxy가 최종 목표에 대한 나의 해결책이 될 수 있다고 믿습니다. 그러나 NSProxy는 'class_replaceMethod'에 비해 훨씬 더 성능에 영향을 미칠 것입니다. NSProxy 메서드는 observe 클래스의 메서드를 관찰하기 때문에 replace 메서드는 대체 된 메서드에만 영향을줍니다. 어쨌든 내가 문제를 해결할 수 없다면 NSProxy 접근법을 사용할 수 있습니다. 다시 한번 감사드립니다. –
'forwardingTargetForSelector :'를 구현하면 훨씬 낮은 오버 헤드로 오버라이드하지 않는 메소드를 전달할 수 있습니다. 파견 방법에 대한 유용한 토론은 http://www.mikeash.com/pyblog/friday-qa-2009-03-27-objective-c-message-forwarding.html을 참조하십시오. –