以编程方式创建新的实例方法来跟踪消息发送



我想设计一个类(TrackingClass),负责跟踪对另一个类(TrackedClass)的某些方法的调用,即设置从我理解的方法。

因此,假设我加载了一个数组,其中包含我感兴趣的TrackedClass @selectors实例方法。这是我想运行的伪代码:

@implementation BCTrackedClass
-(void)doA
{
}
@end

@implementation BCTrackingClass
#import "BCTrackingClass.h"
#import "BCTrackedClass.h"
#include <objc/runtime.h>
#include <objc/objc-runtime.h>
@implementation BCTrackingClass
void myMethodIMP(id self, SEL _cmd);
void myMethodIMP(id self, SEL _cmd) 
{
    //NSLog(@"_cmd : %@",NSStringFromSelector(_cmd));
    [BCTrackingClass logCallForMethod:NSStringFromSelector(_cmd)];
    objc_msgSend(self,
                 NSSelectorFromString([NSString stringWithFormat:@"tracked%@",NSStringFromSelector(_cmd)]));
}
+(void)setUpTrackingForClass:(Class)aClass andMethodArray:(NSArray*)anArray //Array of selectorsStrings of methods to track
{
    for (NSString* selectorString in anArray)
    {
        SEL selector = NSSelectorFromString(selectorString);
        SEL trackedSelector = NSSelectorFromString([NSString stringWithFormat:@"tracked%@",selectorString]);
        class_addMethod(aClass,
                        trackedSelector,
                        (IMP) myMethodIMP, "v@:"); 
        //Swizzle the original method with the tracked one
        Method original = class_getInstanceMethod(aClass,
                        selector);
        Method swizzled = class_getInstanceMethod(aClass,
                        trackedSelector);
        method_exchangeImplementations(original, swizzled);
    }
}
+(void)logCallForMethod:(NSString*)aSelectorString
{
    NSLog(@"%@",aSelectorString);
}
@end

从理论上讲,我只是缺少可以有效地创建这个新实例方法的代码trackedSelector。我能做到吗?

编辑

用一些新信息更新了代码,我越来越近了吗?

编辑 2

我设置了一个带有演示应用程序的 Github 存储库,如果人们想动态尝试他们的想法。来源 : Github 上的 BCTrackingClass

编辑 3

我终于想出了一个代码的工作版本(参见Github存储库,或上面)。我的下一个问题是:我希望我的类是基于实例的(目前,我所有的方法都是类方法),以便我可以将属性@property NSMutableDictionnary*分配给类的实例以进行调用记录。我不知道如何实现这一目标。有没有 ?

是否要对该类的所有对象的所有实例执行此操作?对于某些选择器还是所有选择器?...

如果你想要的是跟踪特定的实例,那么最简单的途径是使用isa旋转,或多或少地这样做(代码绝对未经测试)

@interface ClassTracker
+ (void)trackObject:(id)object;
@end
static const char key;
@implementation ClassTracker
+ (void)trackObject:(id)object
{
    objc_setAssociatedObject(object, &key, [object class], OBJC_ASSOCIATION_ASSIGN);
    object_setClass(object, [ClassTracker class]);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    Class aClass = objc_getAssociatedObject(self, &key);
    return [aClass instanceMethodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
    Class aClass = objc_getAssociatedObject(self, &key);
    // do your tracing here
    object_setClass(self, aClass);
    [invocation invoke];
    object_setClass(self, [ClassTracker class]);
}
// dealloc is magical in the sense that you really want to undo your hooking
// and not resume it ever!
- (void)dealloc
{
    Class aClass = objc_getAssociatedObject(self, &key);
    object_setClass(self, aClass);
    [self dealloc];
}
@end

如果它用于逆向工程或调试目的,那应该(稍作调整)就可以了。

如果您打算快速,则必须执行实例方法重排,了解它们的类型等。

我的"解决方案"有一个缺点,它只会跟踪输入调用,如果选择器调用其他调用,IOW,因为 isa 切换暂停以递归调用,那么在恢复 isa 切换之前,您看不到新的呼叫。

可能有一种方法可以将调用转发到原始类,而无需撤消 isa 滑动,但我认为我懒得搜索它。

相关内容

  • 没有找到相关文章

最新更新