2

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

假设我用我感兴趣@selectors的实例方法加载了一个数组TrackedClass。这是我想运行的伪代码:

@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 repo,或以上)。我的下一个问题是:我希望我的类是基于实例的(目前,我所有的方法都是类方法),这样我就可以为@property NSMutableDictionnary*类的实例分配一个属性以进行调用记录。我不确定如何实现这一目标。有什么想法吗?

4

1 回答 1

0

你想对那个类的所有对象的所有实例都这样做吗?对于某些选择器或所有选择器?...

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

@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 swizzling 被暂停以递归调用,那么在恢复 isa swizzling 之前你看不到新的调用。

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

于 2013-12-02T00:35:17.917 回答