Performing selectors on main thread with NSInvocation
I want to perform animation开发者_Go百科 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?
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];
"
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
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.
精彩评论