记录方法参数的实际类型的宏

时间:2014-02-09 02:47:54

标签: objective-c macros

让我们说这是我的初始化方法

- (id)initWithClient:(id <Client>)client 
      andDataStorage:(DataStorage *)storage
{
    if (self = [super init])
    {
        self.client = client;
        self.storage = storage;
    }

    return self;
}

然后我想通过用定义的宏包装参数来编写一个以某种方式记录传递给方法的参数的宏。这有可能吗?

问题是在运行时,无法找出传递给方法的参数类型。所以我试图找到它周围的黑客,并在编译时这样做。

// somehow achieve this, and log the value inside the  marco
#define INJECT(x) NSLog(@"%@", x)

- (id)initWithClient:(INJECT(id <Client>))client 
      andDataStorage:(INJECT(DataStorage *))storage
{

}

预期登录控制台:

id <Client>
DataStorage *

4 个答案:

答案 0 :(得分:1)

冒着在注释中遇到似乎是交叉线的风险:你可以获得在运行时传递给方法的参数类型。

E.g。

NSMethodSignature *signature = 
    [class methodSignatureForSelector:@selector(someSelector:)];

for(int argument = 2; argument < signature.numberOfArguments; argument++)
{
    const char *argumentType = [signature getArgumentTypeAtIndex:argument];

    // this is where it gets a bit messy...
    if(!strcmp(argumentType, @encode(int))) NSLog(@"an integer");
    if(!strcmp(argumentType, @encode(float))) NSLog(@"a float");
    // ... etc, etc, etc ...
}

对于任何传递的对象,使用[object class],因为所有对象在运行时级别看起来都相同 - 例如NSArray -addObject:;运行时知道将传入一个对象类型,但它可以是任何对象类型。

有关@encode的详细信息,请参阅Apple Type Encodings上的文档。

答案 1 :(得分:0)

虽然不是这个问题的答案。我不建议你做你想问的事。我已经看到很多代码,人们已经记录了每一个方法调用和参数(可怕的过于复杂的Java Enterprise的东西)。结果一直都是粗暴的大型日志,它们几乎无法告诉您,因为找到您所需要的工作量很大。

我的建议是日志记录很重要,但您应该进行有针对性的日志记录,清除显示特定点的相关数据状态,这对理解流程非常重要。

答案 2 :(得分:0)

和其他人一样,我不确定你真正追求的是什么,或者它是否是一个好主意/设计等等。但我想知道你是否正在以错误的方式解决问题。所以,让我们来看看,也许它会对你有所帮助。从我看到你:

  1. 想要找到一些在运行时以字符串形式获取声明的方法参数类型的方法。
  2. 您正试图通过向源添加宏来解决此问题。这告诉我,您不是要为动态加载的二进制库中的方法执行此操作,而是要编译源代码中的方法,并准备进行修改以实现目标。
  3. 看着那边,有什么问题?如果您准备将宏添加到源中,为什么不简单地添加包含所需信息的数据声明 - 从选择器到参数类型的顺序列表的映射为字符串。

    您想要以某种自动方式提取信息并且打算通过某些自动化流程添加宏来解决问题吗?

    您可以通过更改文件扩展名来安排Xcode项目通过其他程序运行源文件。 Apple提供了使用它来预处理字符串文件的示例 - 这些文件通过Ruby脚本提供,该脚本生成一个Xcode然后照常处理的字符串文件。这会满足您的需求吗?你能编写一个脚本/应用程序(不需要在Ruby中),它可以“动态”添加你需要的信息 - 获取源代码,生成修改后的源代码,然后Xcode像往常一样编译吗?请注意,Clang编译器本身被设计为一个库,因此您甚至可以使用它来帮助您解析源代码以提取您所需的信息。

    如果这些方法都不适合考虑调试器在运行时知道正确的类型,那么它将从为其生成的符号信息中获取。提供了库函数来帮助读者调试器信息,因此您应该能够编写使用调试器所用信息的代码。

    希望这些想法对你有所帮助,不过我仍然不清楚你在尝试什么或是否有意义!

答案 3 :(得分:0)

由于objC是动态类型的,所有类都具有类型id。有关已声明类型的信息将被删除。它们只是开发人员的提示,并使编译器能够进行某种类型检查(同样纯粹是为了开发人员的利益)

因此,虽然@encode适用于'灵长类'和结构和东西,但对于所有类都是相同的......因为运行时没有真正的对象类型

'解决方案':手动将方法参数的类名存储在地图中,然后将该信息与@encode; s信息合并以记录这些内容。

工作样本:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

NSDictionary *DDParamsMap(void);
NSDictionary *DDParamsMap() {
    static NSDictionary *dict = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //TODO
        //add all methods that are have objc classes passed
        //add the classes or NSNull
        dict = @{@"Test_initWithArray:data:number:": @[NSArray.class, NSData.class, NSNull.null]};
    });
    return dict;
}

void DDLogParamsOf(Class class, SEL sel);
void DDLogParamsOf(Class class, SEL sel) {
    //
    //try internal lookup first (so we get class names
    //
    NSString *className = @(class_getName(class));
    NSString *methodName = NSStringFromSelector(sel);
    NSString *key = [NSString stringWithFormat:@"%@_%@", className, methodName];
    NSArray *types = DDParamsMap()[key];

    //
    // loop
    //
    NSMethodSignature *signature = [class instanceMethodSignatureForSelector:sel];
    if(!signature) {
        signature = [class methodSignatureForSelector:sel];
    }

    //if the array doesnt have the right number of values, screw it!
    if(types.count != signature.numberOfArguments - 2) {
        types = nil;
    }

    for(int argument = 2; argument < signature.numberOfArguments; argument++) {
        id type = types[argument - 2];
        if(type && ![type isKindOfClass:[NSNull class]]) {
            NSLog(@"class is %@", type);
        }
        else {
            const char *argumentType = [signature getArgumentTypeAtIndex:argument];

            // this is where it gets a bit messy...
            if(!strcmp(argumentType, @encode(int))) NSLog(@"an integer");
            if(!strcmp(argumentType, @encode(float))) NSLog(@"a float");
            if(!strcmp(argumentType, @encode(id))) NSLog(@"it is a class");
            // ... etc, etc, etc ...
        }
    }
}

#define LogParams() DDLogParamsOf(self.class, _cmd);

@interface Test : NSObject

+ (void)testMethofWithFloat:(float)f;

- (id)initWithArray:(NSArray*)a
               data:(NSData*)d
             number:(int)i;

@end

@implementation Test

+ (void)testMethofWithFloat:(float)f {
    LogParams();
}

- (id)initWithArray:(NSArray*)a
               data:(NSData*)d
             number:(int)i
{
    LogParams();
    return nil;
}
@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        [Test testMethofWithFloat:3.0f];
        Test *t = [[Test alloc] initWithArray:@[] data:[NSMutableData data] number:1];
        t = nil;
    }
}