1

Let's say this is my init method

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

    return self;
}

Then I want to write a macro that somehow logs the parameters passed to a method, by wrapping the parameter with a defined macro. Is this possible in any way?

The problem is at runtime it's not possible to find out the type of a parameter passed to a method. So I'm trying to find a hack around it, and do it at compile time.

// 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
{

}

expected log in console:

id <Client>
DataStorage *
jscs
  • 63,694
  • 13
  • 151
  • 195
aryaxt
  • 76,198
  • 92
  • 293
  • 442
  • You can find the class of a method parameter at runtime if it's an Objective-C object. – mipadi Feb 09 '14 at 02:51
  • @mipadi Can you please take a look at a question I posted, and let me know how you would do that. Been trying to get it to work for the past month. http://stackoverflow.com/questions/21639871/objective-c-get-argument-types-of-a-method – aryaxt Feb 09 '14 at 02:52
  • Try `NSLog(@"%@", [client class]);` in the body of your init function. – Turix Feb 09 '14 at 02:54
  • 1
    @aryaxt: Did you try it? It's the right answer. – Crowman Feb 09 '14 at 03:04
  • @PaulGriffiths It has to be dynamic, so that I can replace the NSLog with the actual logic. The whole point is to avoid the manual work. INJECT in this question logs it, but the end solution should store the class type in an NSDictionary in a singleton object – aryaxt Feb 09 '14 at 03:06
  • @PaulGriffiths Still won't do it, It HAS to be a macro, otherwise it requires the method to be executed before that info is stored. I need that info to be stored at compile time, so that at runtime I can use it to inject objects using the correct initializer and correct parameters – aryaxt Feb 09 '14 at 03:12
  • @PaulGriffiths No at runtime given a class it's not possible to determine the type of arguments passed to its methods. Here is the template for the method. Please implement. - (NSArray *)parametersForSelector:(SEL)selector andClass:(Class)class – aryaxt Feb 09 '14 at 03:16
  • @aryaxt: Sure, here you go: http://ideone.com/hlS2iX – Crowman Feb 09 '14 at 03:23
  • Your previous question is about getting the types from _outside_ the method at runtime. This seems to be about getting the class of an argument inside the method; of course that's possible, because an argument is just like a local variable. – jscs Feb 09 '14 at 03:24
  • 1
    You should have a look at [Strict type checking in ObjC via macros](http://stackoverflow.com/q/7838259) and [Part II](http://stackoverflow.com/questions/7890695/strict-type-checking-in-objective-c-part-2?lq=1) – jscs Feb 09 '14 at 03:31
  • Your ultimate goal is to get the class _names_ into a dictionary somewhere? – jscs Feb 09 '14 at 04:01
  • @JoshCaswell Yes that's correct. I am working on a dependency injection framework, it suppose to call the init method and pass parameters to this init method automatically. If i know the type of parameters I can automatically initialize them https://github.com/aryaxt/OCInjection – aryaxt Feb 09 '14 at 04:58

4 Answers4

1

At the risk of running into what appear to be crossed wires in the comments: you can get the parameter types passed to a method at runtime.

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 ...
}

For any passed objects, use [object class] since all objects look the same at the runtime level — think of e.g. NSArray -addObject:; the runtime knows an object type will be passed in but it could be any object type.

See Apple's documentation on Type Encodings for information on what's going on there with those @encodes.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • OP is aware of this functionality; see the linked previous question. – jscs Feb 09 '14 at 03:32
  • Actually, I think this might help. I wasn't aware that you could do that comparison if(!strcmp(argumentType, @encode(int))). Gonna give it a try – aryaxt Feb 09 '14 at 03:34
  • This doesn't seem to do what I want. As you said the encoded value for all classes seem to be the same. In the following NSLog doesn't matter what class you change NSArray to, the result would be the exact same. NSLog(@"[NSArray] : %s", @encode(typeof([NSArray class]))); – aryaxt Feb 09 '14 at 04:33
  • Objects are dynamically typed in Objective-C. You seem to be in a statically typed frame of mind. So, no, there's no compiler support for pretending that the language works in a fundamentally different way than it does. Not only does Objective-C allow object types to be interchanged freely but large parts of the language and framework *rely* on it. You probably want to hack something together with clang — the static analyser — or else stop thinking like this is Java or C++. – Tommy Feb 09 '14 at 19:35
0

Whilst not an answer to the question as such. I would not recommend doing what you are asking about. I've seen far to much code where people have logged every single method call and argument (horribly over-complicated Java Enterprise stuff). The result has always been obscenely large logs that tell you next to nothing because of the amount of work it takes to find what you are after.

My recommendation would be that logging is important, but you should do targeted logging that clearing shows the state of relevant data at specific points which are important to understanding the flow.

drekka
  • 20,957
  • 14
  • 79
  • 135
0

Like others, I'm not sure what you are really after, or whether it is a good idea/design etc. But I wonder whether you are approaching the problem the wrong way. So let's take a look and maybe it will help you. From what I see you:

  1. Want to find some way of obtaining the declared types of method parameters, in the form of strings, at runtime.
  2. You are trying to tackle this by adding macros to the source. This tells me that you are not trying to do this for methods in a binary library that you are dynamically loading, but to methods in source you are compiling and are prepared to modify to achieve your goal.

Looked at that way, what is the problem? If you are prepared to add macros to your source why not simply add data declarations that contain the information you want - a mapping from a selector to an order list of parameter types as strings.

Is the issue that you want to extract the information in some automated way and were intending adding your macros by some automated process?

You can arrange for an Xcode project to run a source file through some other program by changing the file extension. Apple provide examples of using this to pre-process strings files - the files are fed through a Ruby script which produces a strings file which Xcode then handles as usual. Will that address your needs? Could you write a script/application (doesn't need to be in Ruby) which could add the information you need "on the fly" - take source in, produce modified source out which Xcode then compiles as usual? Note that the Clang compiler itself is designed to be called as a library so you can even use it to help you parse your source to extract the information you are after.

If none of those approaches suit consider that the debugger knows the correct types at runtime, and it gets those from the symbol information generated for it. There are library functions provided to help reader debugger information, so you should be able to write code which uses the same information the debugger does.

Hope those ideas help you, though I'm still not clear what you are trying or whether it makes sense!

CRD
  • 52,522
  • 5
  • 70
  • 86
0

due to objC being dynamically typed, all classes have the type id. The information about the declared types is erased. They are merely hints for the developer and to enable the compiler to do some type checking (again purely for the dev's benefit)

So while @encode works for 'primates' and structs and stuff, for classes all is equal... as there are not really object types for runtime

'Solution': Store the class names of method argumentsin a map manually and then COMBINE that info with @encode;s info to log the stuff.

working sample:

#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;
    }
}
Daij-Djan
  • 49,552
  • 17
  • 113
  • 135