Executing selectors in the main thread with NSInvocation

I want to animate in the main thread (because UIKit objects are not thread safe), but prepare them in a separate thread. I have (baAnimation is the CABasicAnimation highlighted and entered earlier):

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

Challenges:

 > #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 great. What am I doing wrong?

+2
source share
3 answers

In addition to [inv retainArguments] (as mentioned by Chris Suter) you also need to pass arguments as pointers to base memory. Referring to 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]; 

"

+6
source

If you have one or more arguments in your NSInvocation, I would recommend creating a new category that calls the selector in the main thread. Here is how I solved it:

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 
+4
source

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

What I would do is save any state that you need in the instance variables, and then when you are ready, just do:

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

Also, the allocation and initialization of CABasicAnimation in the stream is not required (it will not take any noticeable time for this in the main stream) and is still potentially dangerous. Keep your processor busy in a separate thread, but nothing more.

+2
source

All Articles