0

I am running through a list of arguments, though in these arguments the value can be anything from NSInteger, Selector to NSObjects. But if it is an NSObject it needs to be retained properly. I can't simply check if the class is the same as NSObject or if it responds to the retain method, because if you do that on a selector or integer it will simply crash. So how can you still do it? I have no idea.

I even tried to put a @try @catch in it, try to retain if not it's probably an object that doesn't need to be retained. But it crashes immediately too :( No error exception here.


If only I could test if a certain argument has a class, if a class is found I can check it for being a NSObject class, if no class is found it shouldn't be retained either. I found:

object_getClass();

But it crashes when you pass an NSInteger in it.


Looking at the NSInvocation class you can call the retainArguments method, unfortunatly this will crash the app as well. But there is something strange in the description at setArgument:

When the argument value is an object, pass a pointer to the variable (or memory) from which the object should be copied

That would mean there is 'something' that can detect if an argument is an object, but how?


Code (till now)

- (void)addObserver:(NSObject *)observer selector:(SEL)selector arguments:(id)firstObj, ... {
    // Define signature
    NSMethodSignature *signature  = [[observer class] instanceMethodSignatureForSelector:selector];
    NSInvocation      *invocation = [NSInvocation invocationWithMethodSignature:signature];

    // Prepare invocation
    [invocation setTarget:observer];
    [invocation setSelector:selector];

    id        currentObject;
    va_list   argumentsList;
    NSInteger currentIndex = 2;

    if (firstObj) {
        va_start (argumentsList, firstObj);
        while (currentObject = va_arg(argumentsList, id)) {
            [invocation setArgument:&currentObject atIndex:currentIndex];
            currentIndex++;
        }
        va_end(argumentsList);
    }

    // The observer can easily be retained by doing [observer retain];
    // However the arguments may consist of NSIntegers etc. which really don't like
    // to be retained (logically). So I want to skip the argument that don't need
    // retaining.
}

Goal

What I am trying to accomplish is the following:

I have a random method like:

- (void)fetchFruitApples:(NSInteger)amount inRange:(NSRange)range withString:(NSString *)aString {
    //Can I fetch fruit? 
    //If so, execute method. 
    //If not wait for a certain event to occur (without blocking the main thread) 
    //Then retry this method with the arguments passed.
    //Thats why here I want to do [MyObject addObserver:self selector:@selector(fetchFruitApples:inRange:withString:) arguments:amount, range, aString, nil];
}
Evan Cordell
  • 4,108
  • 2
  • 31
  • 47
Mark
  • 16,906
  • 20
  • 84
  • 117
  • What type are you receiving these values as? `void*`? Usually APIs that allow things like this require the types to be indicated. I'm not a C guru, but as far as I know, when you receive a `void*` it's just a pointer to arbitrary memory and you need to know what it is via some other means. Maybe post some code? – d11wtq Nov 25 '10 at 12:45
  • Also, this would be a great place to use blocks... if your code doesn't need to support iOS 3 or anything before leopard. – Kenny Winker Nov 25 '10 at 13:50
  • True, though I still want to support iPad 3.2 since 4.2 has just been released. – Mark Nov 25 '10 at 13:53

5 Answers5

0

How about you pass your numbers as NSNumber objects, that way you can be sure everything you get is an object and will respond to things like [arg retain] and [arg isKindOfClass:[NSNumber class]]

I don't know this for sure, but I'm going to make an educated guess that there is no way to do this. You might be able to tell between ints and objects, but to tell between a pointer that points to a selector, and a pointer that points to an object? Doubtful.

Kenny Winker
  • 11,919
  • 7
  • 56
  • 78
  • Hey Kenny thanks for taking the time to respond. The thing is I can't simply make a NSNumber of it because if I pass a NSRange with the arguments I will will have a problem. Perhaps I should edit my question with a goal of what I want to accomplish. – Mark Nov 25 '10 at 13:52
  • What about an NSOperationQueue? – Kenny Winker Nov 25 '10 at 14:03
  • The problem is that after the thread is done observing and it detects that it can continue. It should recall the method. However when calling performMethodOnMainThread it can only pass some argument, two tops. So what if my method has more arguments then that? In my fetchFruit example it has 3. While I can only pass two in withObject:withObject. – Mark Nov 25 '10 at 14:06
  • "An NSInvocation is an Objective-C message rendered static, that is, it is an action turned into an object." – Kenny Winker Nov 25 '10 at 14:09
0

Your stated goal makes me think you should probably explore NSOperationQueue.

"This brings me to my next point NSOperation dependencies. NSThread has no built in mechanism for adding dependencies as of right now. However NSOperation has the - (void)addDependency:(NSOperation *)operation method which allows for an easy mechanism (when used with NSOperationQueue) for dependency management. So with that lets get into NSOperationQueue..."

From http://cocoasamurai.blogspot.com/2008/04/guide-to-threading-on-leopard.html

Kenny Winker
  • 11,919
  • 7
  • 56
  • 78
0

Your best bet is probably just to use a format string, much as you would with [NSString stringWithFormat:]. The caller always knows the correct types, so why not just get it to pass along that information?

For example, if you change your method signature to:

- (void)addObserver:(NSObject *)observer selector:(SEL)selector argumentFormat:(NSString*)format arguments:(id)firstObj, ... {

  // parse format

}

format would be something like: isS and would mean the first parameter is an integer, the second is a string and the third is a selector (i.e., retain the second parameter but not the other two).

Stephen Darlington
  • 51,577
  • 12
  • 107
  • 152
  • I have tried that, however if you use [NSString stringWithFormat:@"%@", someInteger] it crashes cause you have to use %d instead and since I dont know if it's an Integer or an Object I can't react on it. Though I noticed that if a property does not have a class it isn't an object, thou how can you detect if a property doesnt have a class? – Mark Nov 25 '10 at 16:25
  • No, I don't mean _use_ `NSString stringWithFormat:`, I mean something _like_ that. I've clarified my answer. – Stephen Darlington Nov 25 '10 at 18:17
0

Probably the easiest solution:

[myInvocation setRetainArguments:YES];

This will tell the invocation instance to retain any object argument you set. So no job at all from your point of view.

If you need to do it manually then you should look at the method -[NSMethodSignature getArgumentTypeAtIndex:]. It will return a char * with the type encoded in with the same format as @encode() use. Could be used as this:

char* type = [myMethodSignature getArgumentTypeAtIndex:3];
if (strcmp(type, @encode(id)) == 0) {
  // It is an object!
}

Lastly I have already done the work you probably need to easily create NSInvocation instances to invoke methods with any kind of arguments with a single statement. Both on background-/main-thread, with delay, and on operation queues.

I have blogged on the topic, and the full source code is available from here: http://blog.jayway.com/2010/03/30/performing-any-selector-on-the-main-thread/

and more here: http://blog.jayway.com/2010/08/19/future-cocoa-operation/

PeyloW
  • 36,742
  • 12
  • 80
  • 99
  • If you turn retainArguments on while submitting cross variables (NSInteger and NSString *) it still crashes, so at this point I would still need to manualy retain the vars I pass a long while I would love to have the method detect itself rather it should retain or not. – Mark Nov 25 '10 at 16:23
  • Are you telling me that you sometime pass a `NSInteger` and sometimes a `NSString*` into the same argument? If so that is just plain wrong. – PeyloW Nov 26 '10 at 10:26
0

Finally The Solution

After a hard day of hoping to discover that what seemed impossible I finally have the solution special thanks to PeyloW for pointing me in the right direction. Read his blog at: http://blog.jayway.com/2010/03/30/performing-any-selector-on-the-main-thread/

The key was:

const char *type   = [signature getArgumentTypeAtIndex:index];
NSString *dataType = [[[NSString alloc] initWithCString:type] autorelease];
if ([dataType isEqualToString:@"@"]) // The argument is an object!
Mark
  • 16,906
  • 20
  • 84
  • 117