Performing selectors on main thread with NSInvocation
Asked Answered
C

3

2

I want to perform animation on main thread (cause UIKit objects are not thread-safe), but prepare it in some separate thread. I have (baAnimation - is CABasicAnimation allocated & inited before):

SEL animationSelector = @selector(addAnimation:forKey:);
NSString *keyString = @"someViewAnimation";

NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[workView.layer methodSignatureForSelector:animationSelector]];
[inv setTarget:workView.layer];
[inv setSelector:animationSelector];
[inv setArgument:baAnimation atIndex:2];
[inv setArgument:keyString atIndex:3];
[inv performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:NO];

I get:

*** +[NSCFString length]: unrecognized selector sent to class 0x1fb36a0

Calls:

>     #0 0x020984e6 in objc_exception_throw
>     #1 0x01f7e8fb in +[NSObject doesNotRecognizeSelector:]
>     #2 0x01f15676 in ___forwarding___
>     #3 0x01ef16c2 in __forwarding_prep_0___
>     #4 0x01bb3c21 in -[CALayer addAnimation:forKey:]
>     #5 0x01ef172d in __invoking___
>     #6 0x01ef1618 in -[NSInvocation invoke]

But [workView.layer addAnimation:baAnimation forKey:@"someViewAnimation"]; works fine. What am I doing wrong?

Cowans answered 3/6, 2010 at 7:11 Comment(0)
D
6

In addition to [inv retainArguments] (as mentioned by Chris Suter) you also need to pass the arguments as pointers to the underlying memory. Citing the API:

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

NSArray *anArray;  
[invocation setArgument:&anArray atIndex:3];  

"

Dreda answered 16/9, 2010 at 19:21 Comment(1)
This should be the accepted correct answer, as this is the bigger problem (not passing the right stuff at all, as opposed to a memory management problem)Justinejustinian
G
4

If you have one or more arguments in your NSInvocation then I would recommend creating a new category that invokes the selector on main thread. This is how I solved this:

Example
NSInvocation+MainThread.h

#import <Foundation/Foundation.h>

@interface NSInvocation (MainThread)
- (void)invokeOnMainThreadWithTarget:(id)target;
@end

NSInvocation+MainThread.m

#import "NSInvocation+MainThread.h"

@implementation NSInvocation (MainThread)

- (void)invokeOnMainThreadWithTarget:(id)target {
    [self performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:target waitUntilDone:YES];
}

@end
Gleam answered 8/12, 2011 at 16:20 Comment(0)
A
2

You either need to add [inv retainArguments] or change the waitUntilDone parameter to YES, but before you do that, let me just say that what you’ve done is pretty unreadable.

What I would do is store whatever state you need in instance variables and then when you're ready, just do:

[self performSelectorOnMainThread:@selector (startAnimation) withObject:nil waitUntilDone:NO];

Also allocating and initialising a CABasicAnimation on a thread is unnecessary (it won't take any noticeable time to do it on the main thread), and is still potentially dangerous. Keep processor intensive work on a separate thread, but not anything else.

Anhydride answered 3/6, 2010 at 7:38 Comment(3)
Thanks a lot. Really I'm quite agree with you and this task has "theoretical" goals (just to try this way).Cowans
@Chris Suter - Answer is good, but the rest of the advice goes against accepted wisdom about threading. It is far, far easier to pass parameters at well-defined ingresses and egresses between threads than to try to synchronize shared resource access. This isn't just me talking. Here's Apple's own advice on the subject developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…Adulterine
@Adulterine I think it depends on the situation. Generally speaking you’re right but the number one goal should be to maximise readability and I think a common mistake that many programmers make is to blindly follow patterns or rules. In this situation, and it’s difficult to tell exactly without knowing more about the problem, I suspect that using instance variables to record state is going to be simpler and more maintainable than doing what you suggest.Anhydride

© 2022 - 2024 — McMap. All rights reserved.