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

时间:2013-11-06 18:30:07

标签: objective-c objective-c-runtime swizzling

我想设计一个类(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

如果人们想要动态尝试他们的想法,我会在Demo应用程序中设置一个Github存储库。 资料来源:BCTrackingClass on Github

编辑3

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

1 个答案:

答案 0 :(得分: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调配暂停以递归呼叫,然后在恢复之前你不会看到新的呼叫isa swizzling。

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