Lom*_*baX 5 objective-c variadic-functions objective-c-runtime swizzling ios
我正在实现一个"Code Injector Class",通过方法调配可以让你做到这样的事情:
FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{
NSLog(@"This code should be injected");
}];
Run Code Online (Sandbox Code Playgroud)
aSelector可以是具有可变数量的参数和变量返回类型的方法.参数/和返回类型可以是对象或基本类型.
首先,我附上代码,injectCodeBeforeSelector:让你了解我在做什么(我删除了代码的有趣部分):
- (void)injectCodeBeforeSelector:(SEL)method code:(void (^)())completionBlock
{
NSString *selector = NSStringFromSelector(method);
[self.dictionaryOfBlocks setObject:completionBlock forKey:selector];
NSString *swizzleSelector = [NSString stringWithFormat:@"SWZ%@", selector];
// add a new method to the swizzled class
Method origMethod = class_getInstanceMethod(self.mainClass, NSSelectorFromString(selector));
const char *encoding = method_getTypeEncoding(origMethod);
[self addSelector:NSSelectorFromString(swizzleSelector) toClass:self.mainClass methodTypeEncoding:encoding];
SwizzleMe(self.mainClass, NSSelectorFromString(selector), NSSelectorFromString(swizzleSelector));
}
-(void)addSelector:(SEL)selector toClass:(Class)aClass methodTypeEncoding:(const char *)encoding
{
class_addMethod(aClass,
selector,
(IMP)genericFunction, encoding);
}
Run Code Online (Sandbox Code Playgroud)
基本上,我使用class_addMethod将伪/ swizzle方法添加到目标类,然后进行调整.该方法的实现设置为这样的函数:
id genericFunction(id self, SEL cmd, ...) {
// call the block to inject
...
// now forward the message to the right method, since method are swizzled
// I need to forward to the "fake" selector SWZxxx
NSString *actualSelector = NSStringFromSelector(cmd);
NSString *newSelector = [NSString stringWithFormat:@"SWZ%@", actualSelector];
SEL finalSelector = NSSelectorFromString(newSelector);
// forward the argument list
va_list arguments;
va_start ( arguments, cmd );
return objc_msgSend(self, finalSelector, arguments);
}
Run Code Online (Sandbox Code Playgroud)
现在问题是:我在最后一行有一个EXC_BAD_INSTRUCTION(objc_msgSend_corrupt_cache_error()).如果我将va_list参数转发给假选择器,则会出现问题.如果我将最后一行更改为
return objc_msgSend(self, cmd, arguments);
Run Code Online (Sandbox Code Playgroud)
没有错误,但显然无限递归开始.
我试过:
但没有结果.我认为问题与这个事实有关:va_list不是一个简单的指针,它可以类似于相对于方法的堆栈地址的偏移量.所以,我不能用另一个函数的arg列表调用一个函数的objc_msgsend(swizzled函数)(非swizzled函数).
我试图改变方法并复制NSInvocation中的所有参数,但是我在管理调用的返回值时遇到了其他问题,甚至逐个复制参数(管理所有不同类型)需要大量代码,所以我更喜欢返回这种方法,对我来说似乎更清洁(imho)
你有什么建议吗?谢谢
这里的主要问题是如何将变量参数传递给函数。
通常,它们在堆栈上传递,但据我所知,ARM ABI 的情况并非如此,至少在可能的情况下使用寄存器。
所以你这里有两个问题。
首先,编译器在执行您自己的方法的代码时可能会弄乱这些寄存器。
我对此不太确定,因为我对 ARM ABI 不太了解,所以你应该检查参考资料。
第二个问题,更重要的是,您实际上将单个变量参数传递给obj_msgSend(the va_list)。因此,目标方法显然不会收到它所期望的结果。
想象一下以下情况:
void bar( int x, ... )
{}
void foo( void )
{
bar( 1, 2, 3, 4 );
}
Run Code Online (Sandbox Code Playgroud)
在 ARM 上,这对于函数来说意味着foo:
movs r0, #1
movt r0, #0
movs r1, #2
movt r1, #0
movs r2, #3
movt r2, #0
movs r3, #4
movt r3, #0
bl _bar
Run Code Online (Sandbox Code Playgroud)
变量参数传入R1、R2和R3,以及int中的参数R0。
因此,在您的情况下,作为objc_msdSend用于调用您的方法的调用,R0应该是目标对象的指针、R1选择器的指针,并且变量参数应该从 开始R2。
当您向 发出自己的调用时objc_msdSend,您至少会R2用您的 . 覆盖 ,的内容va_list。
您应该尽量不关心变量参数。幸运的是,如果调用之前的代码objc_msgSend(获得最终选择器的地方)没有弄乱这些寄存器,那么正确的值应该仍然存在,使它们可用于目标方法。
当然,这只适用于真实设备,而不适用于模拟器(模拟器是 x86,因此这里的可变参数在堆栈上传递)。
| 归档时间: |
|
| 查看次数: |
2130 次 |
| 最近记录: |