views:

108

answers:

2

I have an objective-c class whose methods I only ever want to be called from the main thread.

I could achieve this by adding something like this to each selector:

- (void) exampleSelector: (id) param {
    if (![NSThread isMainThread]) {
        [self peformSelectorOnMainThread:@selector(exampleSelector:) withObject:param waitUntilDone:YES];
        return;
    }
    // Do stuff it's not safe to do outside the main thread
}

However it seems a bit of a pain to add this to every single selector. Is there any way I can automatically intercept all calls to objects of this class, check what thread it's in, and use performSelectorOnMainThread if it's not the main thread?

+1  A: 

Hmm. I'm not sure if it's possible to do this; I can't think of a way to do so. What I would be inclined to do instead is to replace the object with a proxy. The proxy will be responsible for forwarding invocations to the object in the correct thread.

@interface Forwarder : NSObject
{
    id recipient;
}

- (Forwarder *)initWithRecipient:(id)inRecipient;

@end

@implementation Forwarder

- (Forwarder *)initWithRecipient:(id)inRecipient {
    [inRecipient retain];
    recipient = inRecipient;
    return self;
}

- (void)dealloc {
    [recipient release];
    [super dealloc];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([recipient respondsToSelector: [anInvocation selector]]) {
        if (![NSThread isMainThread]) {
            // Note: only works with methods that take one argument. Should add
            // error handling for other methods
            id argument;
            [invocation getArgument: &argument atIndex: 2];
            [recipient performSelectorOnMainThread: [anInvocation selector] withObject: argument waitUntilDone: YES];
        } else {
            [anInvocation invokeWithTarget:recipient];
        }
    } else {
        [super forwardInvocation:anInvocation];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([recipient respondsToSelector: aSelector])
        return [recipient methodSignatureForSelector: aSelector];
    else
        return [super methodSignatureForSelector: aSelector];
}

@end
Brian Campbell
A: 

Perhaps instead you should place a restriction on the callers of the method not to invoke it from an arbitrary thread. If the main thread is blocked waiting on a result from thread B, and thread B invokes a method like the one in your example, you'll have a deadlock.

Jon Hess