开发者

Objective-C detect whether an argument can be retained?

开发者 https://www.devze.com 2023-01-27 02:48 出处:网络
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

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 cert开发者_运维技巧ain 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];
}


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.


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


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/


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


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!
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号