Using performSelector:withObject:afterDelay: with non-object parameters - iphone

I want to invoke setEditing:animated: on a table view with a slight delay. Normally, I'd use performSelector:withObject:afterDelay: but
pSwOaD only accepts one parameter
the second parameter to setEditing:animated: is a primitive BOOL - not an object
In the past I would create a dummy method in my own class, like setTableAnimated and then call [self performSelector:#selector(setTableAnimated) withObject:nil afterDelay:0.1f but that feels kludgy to me.
Is there a better way to do it?

Why not use a dispatch_queue ?
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[tableView setEditing …];
});

You need to use NSInvocation:
See this code, taken from this answer, and I've changed it slightly to match your question:
BOOL yes = YES;
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self.tableView methodSignatureForSelector:#selector(setEditing:Animated:)]];
[inv setSelector:#selector(setEditing:Animated:)];
[inv setTarget:self.tableView];
[inv setArgument:&yes atIndex:2]; //this is the editing BOOL (0 and 1 are explained in the link above)
[inv setArgument:&yes atIndex:3]; //this is the animated BOOL
[inv performSelector:#selector(invoke) withObject:nil afterDelay:0.1f];

The selector setEditing:animated: is not compatible with performSelector:withObject:afterDelay. You can only call methods with 0 or 1 arguments and the argument (if any) MUST be an object. So your workaround is the way to go. You can wrap the BOOL value in an NSValue object and pass it to your setTableAnimated method.

If you can get your head around it, use an NSInvocation grabber to make an invocation object & call it with a delay with 1 line instead of many: http://overooped.com/post/913725384/nsinvocation

Related

NSInvocation pass C-arrays to Objective-C method

I want to pass C-arrays to a method in Objective-C after a delay. Typically I could performSelector:withObject:afterDelay but I can't change the arrays in any way or convert them to NSMutableArrays, NSDictionaries or any other Cocoa object - they need to be C Arrays. In my research here on StackOverflow and Google I've found that one way to pass a C primitive is to wrap them in an NSInvocation. I've tried doing this with the code below and setting the arguments as pointers to the arrays being passed.
float gun1_vertex[24][8] = { {data,...data},{data,...data}, ... {data,...data} };
float gunDown1_vertex[24][8] = { {data,...data},{data,...data}, ... {data,...data} };
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:#selector(testMethod:secondCArray:)]];
[inv setSelector:#selector(testMethod:secondCArray:)];
[inv setTarget:self];
[inv setArgument:&gun1_vertex[0][0] atIndex:2];
[inv setArgument:&gunDown1_vertex[0][0] atIndex:3];
[inv performSelector:#selector(invoke) withObject:nil afterDelay:0.1f];
My test app keeps crashing when I attempt to print a few of the values from the passed arrays in the method below. I'm probably just missing something completely obvious. Can somebody please shed some light here?
- (void)testMethod:(float *)test secondCArray:(float *)test2 {
for ( int a = 0 ; a < 10 ; a++ ) {
NSLog(#"%f %f",test[a],test2[a]);
}
}
You could do something like this:
-(void) testMethod:(NSData *) arrayone secondArray:(NSData *) arraytwo
{
float **gun1_vertex = (float **)[arrayone bytes];
float **gunDown1_vertex = (float **)[arraytwo bytes];
// ...
}
NSData *gun1data = [NSData dataWithBytes:(void *)gun1_vertex length:sizeof(float) * 24 * 8];
NSData *gun1downData = [NSData dataWithBytes:(void *)gunDown1_vertex length:sizeof(float) * 24 * 8];
[inv setArgument:&gun1data atIndex:2];
[inv setArgument:&gun1downData atIndex:3];
Your problem is not enough indirection. setArgument takes a pointer to the location of the value you wish to set as the argument, and the value in your case is also a pointer... Using the passed pointer and the type information setArgument can copy the correct number of bytes.
So you need something like:
float *gun1_vertex_pointer = &gun1_vertex[0][0];
[inv setArgument:&gun1_vertex_pointer atIndex:2];
To make the call after a delay, use dispatch_after block:
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self testMethod:&gun1_vertex[0][0] secondCArray:&gunDown1_vertex[0][0]];
});

Can I pass a function name as an argument ?

I want to make this class dynamically perform functions on itself by passing different names to it. Is that possible ? Or rather: How is it possible ?
-(id)initWithMethod:(NSString*)method{
if ((self = [super init])){
[self method];
}
return self;
}
-(void) lowHealth {
CCSprite *blackScreen = [CCSprite spriteWithFile:#"blackscreen.png"];
blackScreen.anchorPoint = ccp(0,0);
[self addChild:blackScreen];
id fadeIn = [CCFadeIn actionWithDuration:1];
id fadeOut = [CCFadeOut actionWithDuration:1];
id fadeInAndOut = [CCRepeatForever actionWithAction:[CCSequence actions:fadeIn, fadeOut, nil]];
[blackScreen runAction:fadeInAndOut];
}
You should use performSelector and get the selector from your NSString using NSSelectorFromString:
[self performSelector:NSSelectorFromString(method)];
instead of [self method];
The standard way is using Selectors as mentioned in Matteo's answer.
You can also look at Objective-C Blocks. They are becoming very common in the CocoaTouch APIs and you can do some very slick things with them. The resulting architecture of your class is often easier to understand IMO.
For example this method from UIView
+ (void)animateWithDuration:(NSTimeInterval)duration
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion
Takes two block, one that is runs the code for the actual animation, and one to for the code after the animation is complete. You could call this with block variables or by writing code inline:
...animations:^{
// animation code
}
completion:^(BOOL finished) {
// completion code
}
The receiving method (in this case animateWithDuration:...) would simply call these blocks at some point like so:
animations();

iOS - How to implement a performSelector with multiple arguments and with afterDelay?

I am an iOS newbie. I have a selector method as follows -
- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{
}
I am trying to implement something like this -
[self performSelector:#selector(fooFirstInput:secondInput:) withObject:#"first" withObject:#"second" afterDelay:15.0];
But that gives me an error saying -
Instance method -performSelector:withObject:withObject:afterDelay: not found
Any ideas as to what I am missing?
Personally, I think that a closer solution to your needs is the use of NSInvocation.
Something like the following will do the work:
indexPath and dataSource are two instance variables defined in the same method.
SEL aSelector = NSSelectorFromString(#"dropDownSelectedRow:withDataSource:");
if([dropDownDelegate respondsToSelector:aSelector]) {
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[dropDownDelegate methodSignatureForSelector:aSelector]];
[inv setSelector:aSelector];
[inv setTarget:dropDownDelegate];
[inv setArgument:&(indexPath) atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
[inv setArgument:&(dataSource) atIndex:3]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
[inv invoke];
}
Because there is no such thing as a [NSObject performSelector:withObject:withObject:afterDelay:] method.
You need to encapsulate the data you want to send along into some single Objective C object (e.g. a NSArray, a NSDictionary, some custom Objective C type) and then pass it through the[NSObject performSelector:withObject:afterDelay:] method that is well known and loved.
For example:
NSArray * arrayOfThingsIWantToPassAlong =
[NSArray arrayWithObjects: #"first", #"second", nil];
[self performSelector:#selector(fooFirstInput:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:15.0];
You can package your parameters into one object and use a helper method to call your original method as Michael, and others now, have suggested.
Another option is dispatch_after, which will take a block and enqueue it at a certain time.
double delayInSeconds = 15.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self fooFirstInput:first secondInput:second];
});
Or, as you've already discovered, if you don't require the delay you can just use - performSelector:withObject:withObject:
The simplest option is to modify your method to take a single parameter containing both arguments, such as an NSArray or NSDictionary (or add a second method that takes a single parameter, unpacks it, and calls the first method, and then call the second method on a delay).
For instance, you could have something like:
- (void) fooOneInput:(NSDictionary*) params {
NSString* param1 = [params objectForKey:#"firstParam"];
NSString* param2 = [params objectForKey:#"secondParam"];
[self fooFirstInput:param1 secondInput:param2];
}
And then to call it, you can do:
[self performSelector:#selector(fooOneInput:)
withObject:[NSDictionary dictionaryWithObjectsAndKeys: #"first", #"firstParam", #"second", #"secondParam", nil]
afterDelay:15.0];
- (void) callFooWithArray: (NSArray *) inputArray
{
[self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}
- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{
}
and call it with:
[self performSelector:#selector(callFooWithArray) withObject:[NSArray arrayWithObjects:#"first", #"second", nil] afterDelay:15.0];
You can find all the types of provided performSelector: methods here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html
There are a bunch of variations but there isn't a version that takes multiple objects as well as a delay. You'll need to wrap up your arguments in an NSArray or NSDictionary instead.
- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
– performSelector:withObject:afterDelay:
– performSelector:withObject:afterDelay:inModes:
– performSelectorOnMainThread:withObject:waitUntilDone:
– performSelectorOnMainThread:withObject:waitUntilDone:modes:
– performSelector:onThread:withObject:waitUntilDone:
– performSelector:onThread:withObject:waitUntilDone:modes:
– performSelectorInBackground:withObject:
I dislike the NSInvocation way, too complex. Let’s keep it simple and clean:
// Assume we have these variables
id target, SEL aSelector, id parameter1, id parameter2;
// Get the method IMP, method is a function pointer here.
id (*method)(id, SEL, id, id) = (void *)[target methodForSelector:aSelector];
// IMP is just a C function, so we can call it directly.
id returnValue = method(target, aSelector, parameter1, parameter2);
I just did some swizzling and needed to call the original method. What I did was making a protocol and cast my object to it.
Another way is to define the method in a category, but would need suppression of a warning (#pragma clang diagnostic ignored "-Wincomplete-implementation").
A simple and reusable way is to extend NSObject and implement
- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments;
something like:
- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments
{
NSMethodSignature *signature = [self methodSignatureForSelector: aSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
[invocation setSelector: aSelector];
int index = 2; //0 and 1 reserved
for (NSObject *argument in arguments) {
[invocation setArgument: &argument atIndex: index];
index ++;
}
[invocation invokeWithTarget: self];
}
I would just create a custom object holding all my parameters as properties, and then use that single object as the parameter

(iphone) nsInvocation leaks .. maybe the passed arguments?

I'm calling a selector on background thread,
The selector has NSAutorelasePool around it.
I guess the arguments I pass to the selector is causing the problem.
How should I deal with it?
SEL theSelector;
NSMethodSignature *aSignature;
NSInvocation *anInvocation;
theSelector = #selector(changeColor:forColorString:);
aSignature = [[animationData class] instanceMethodSignatureForSelector:theSelector];
anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:animationData];
// indexes for arguments start at 2, 0 = self, 1 = _cmd
[anInvocation setArgument:&currentColor atIndex:2];
[anInvocation setArgument:&nsColorString atIndex:3];
[anInvocation performSelectorInBackground:#selector(invoke) withObject:NULL];
When you tell the invocation to perform invoke in the background, the new thread is created with invoke being the first method called. Invoke does not create an autorelease pool, so anything autoreleased during that method will be leaked.
To fix this, use a wrapper method to perform the invocation.
- (void)performInvocation:(NSInvocation *)anInvocation {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
[anInvocation invoke];
[pool release];
}
//where you were performing the invoke before:
[self performSelectorInBackground:#selector(performInvocation:) withObject:anInvocation];
In addition to what ughoavgfhw said, you also need to call [anInvocation retainArguments] if you intend to set objects as arguments and pass to a background thread.

How to call setNeedsDisplayInRect using performSelectorOnMainThread?

How to call setNeedsDisplayInRect using performSelectorOnMainThread? The problem is rect. I do not know how to pass the rect in performSelectorOnMainThread method. This method ask NSObject, but CGRect is not NSObject, it is just structure *.
//[self setNeedsDisplayInRect:rect];
[self performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:0 waitUntilDone:YES];
}
-(void)drawRect:(CGRect)rect {
/// drawing...
}
I need to invoke setNeedsDisplayInRect method in MainThread from not Main Thread.
Does anyone know how to do it?????????? Thanks in advance..
Really Thanks.
If you're on iOS 4.0 or later, you can use the following
dispatch_async(dispatch_get_main_queue(), ^{
[self setNeedsDisplayInRect:theRect];
});
On iOS 3.2 and earlier, you can set up an NSInvocation and run that on the main thread:
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:#selector(setNeedsDisplayInRect:)]];
[invocation setTarget:self];
[invocation setSelector:#selector(setNeedsDisplayInRect:)];
// assuming theRect is my rect
[invocation setArgument:&theRect atIndex:2];
[invocation retainArguments]; // retains the target while it's waiting on the main thread
[invocation performSelectorOnMainThread:#selector(invoke) withObject:nil waitUntilDone:YES];
You may want to set waitUntilDone to NO unless you absolutely need to wait for this call to finish before proceeding.