I'm trying to pass an indirect pointer to NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: as follows:
-(void)assignResultAfterDelay:(Result **)resultPtr {
[NSTimer scheduledTimerWithTimeInterval:1000
target:self
selector:#(assignResult:)
userInfo:resultPtr // how to do this?
repeats:NO];
}
This doesn't work because of the cast from (Result * __strong *) to id, which gives an "Implicit conversion of an indirect pointer to an Objective-C pointer to 'id'" error.
Variations and combinations of bridged casts such as (__bridge id)resultPtr, using objc_unretainedPointer(resultPtr), or changing the pointer type to (Result * __weak *) have not helped. What is the right way to do this?
I suppose I could create a wrapper class that holds my indirect pointer and send an instance of that, but that seems ugly. Is there a better way to do it?
I think I figured it out. NSInvocation lets me compile:
-(void)assignResultAfterDelay:(Result * __strong *)resultPtr {
NSMethodSignature *sig = [self methodSignatureForSelector:#selector(assignResult:)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
[invocation setTarget:self];
[invocation setSelector:#selector(assignResult:)];
[invocation setArgument:resultPtr atIndex:0];
[NSTimer scheduledTimerWithTimeInterval:1000 invocation:inv repeats:NO];
}
// where assign result looks like:
-(void)assignResult:(Result * __strong *)resultPtr { ... }
Related
What will the return value for performSelector: if I pass a selector that returns a primitive type (on object), such as 'week' on NSDateComponents (which will return an int)?
An example of using NSInvocation to return a float:
SEL selector = NSSelectorFromString(#"someSelector");
if ([someInstance respondsToSelector:selector]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[[someInstance class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:someInstance];
[invocation invoke];
float returnValue;
[invocation getReturnValue:&returnValue];
NSLog(#"Returned %f", returnValue);
}
I think you cannot get the return value from performSelector. You should look into NSInvocation.
New answer to old question~
There is a more concise way of getting the return value from performSelector
NSInvocationOperation *invo = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(height) object:nil];
[invo start];
CGFloat f = 0;
[invo.result getValue:&f];
NSLog(#"operation: %#", #(f));
in which
- (CGFloat)height {
return 42;
}
output
2017-03-28 16:22:22.378 redpacket[46656:7361082] operation: 42
To answer the second part of the question, another way to invoke a selector that returns a primitive would be to get a function pointer and invoke it as such, as in (assuming someSelector returns a float and has no arguments);
SEL selector = NSSelectorFromString(#"someSelector");
float (*func)(id,SEL) = (float (*)(id,SEL))[someInstance methodForSelector: selector];
printf("return value is: %f", (func)(someInstance, selector));
I tried the NSInvocation implemented as suggested by dizy, its working as expected.
I also tried the another way i.e.
int result = objc_msgSend([someArray objectAtIndex:0], #selector(currentPoint));
in the above case, we are bypassing the compiler and inserting objc_msgSend call explicitly as described in the blog:
http://www.cocoawithlove.com/2011/06/big-weakness-of-objective-c-weak-typing.html
In this case, I got the following warning:
Implicitly declaring library function 'objc_msgSend' with type 'id (id, SEL, ...)'
which is obvious because we are calling library function directly.
So, I implemented with the NSInvocation which is working perfectly fine for me.
I know how to use:
[self method:object];
But is it possible to get a SEL object of this?
SEL method = #selector(method:object);
Doesn't work.
Thanks :)
A SEL is just the selector - the name of the message that's sent. To capture a specific instance of that message, its arguments, and its return value as an object, you need to use NSMethodSignature and NSInvocation. An example, based on your hypothetical -method:object above:
NSMethodSignature *sig = [SomeClass instanceMethodSignatureForSelector:#selector(method:)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
// Assume that someObject is an instance of SomeClass
[inv setTarget:someObject];
// Assume an "id object" declared elsewhere.
// Also note that self & _cmd are at indices 0 & 1, respectively
[inv setArgument:&object atIndex:2]
// Some time later...
[inv invoke];
Note that, because an NSInvocation is an object, it doesn't have to be invoked immediately. It can be stored for later use, and usually is - there are far easier ways to send a message if one wants to do so immediately. Cocoa's standard undo/redo machinery, for example, is based on storing and invoking NSInvocations.
A #selector is something that is of another method or function.
Take this for an example:
-(IBAction)timerStart {
timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(targetMethod:)
userInfo:nil
repeats:NO];
}
-(void)targetMethod:(id)sender {
[timer invalidate];
timer = nil;
}
As you can see, the selector (targetMethod:) is being called to action after two seconds of the NSTimer is run. The targetMethod: is a (void)function:(id)sender and therefore that is run.
In your case, what I think you're trying to accomplish is
[self performSelector:#selector(methodName:)];
I don't understand why we have to call the setSelector method on NSInvocation objects when that information is already passed via the invocationWithMethodSignature.
To create an NSInvocation object we do the following:
SEL someSelector;
NSMethodSignature *signature;
NSInvocation *invocation;
someSelector = #selector(sayHelloWithString:);
//Here we use the selector to create the signature
signature = [SomeObject instanceMethodSignatureForSelector:someSelector];
invocation = [NSInvocation invocationWithMethodSignature:signature];
//Here, we again set the same selector
[invocation setSelector:someSelector];
[invocation setTarget:someObjectInstance];
[invocation setArgument:#"Loving C" atIndex:2];
Notice that we passed the selector to [SomeObject instanceMethodSignatureForSelector: someSelector]; and again to [invocation setSelector:someSelector];.
Is there something I'm missing?
A signature is not a selector. A selector is the name of a message. The signature defines the parameters and the return value. You can have many selectors with the same signature, and vice versa. If you look at NSMethodSignature, you will note that there is no -selector method; signatures do not carry around a particular selector.
Consider the following
- (void)setLocation:(CGFloat)aLocation;
- (void)setLocation:(MyLocation*)aLocation;
They have the same selector #selector(setLocation:), but different signatures.
- (void)setX:(CGFloat)x;
- (void)setY:(CGFloat)y;
These have the same signature, but different selectors.
Selectors from the ObjC Programming Language may be a useful reference for understanding this.
A method signature only defines the return type, and the number and type of arguments. It doesn't include anything about the selector name. For example, all of these methods have the same signature, despite having different selectors:
-(void) foo:(NSString*)fooString;
-(void) bar:(NSString*)barString;
-(void) baz:(NSString*)bazString;
This is kind of a side-answer, but the fact that you can do the following helped me understand better the separation between method signatures and selectors.
This code is within a View Controller
NSMethodSignature *sig = nil;
sig = [[self class] instanceMethodSignatureForSelector:#selector(viewDidAppear:)];
NSInvocation *myInvocation = nil;
myInvocation = [NSInvocation invocationWithMethodSignature:sig];
[myInvocation setTarget:_somePopoverController];
[myInvocation setSelector:#selector(dismissPopoverAnimated:)];
BOOL animate = YES;
[myInvocation setArgument:&animate atIndex:2];
[myInvocation invoke];
Since UIViewController's viewDidAppear:
and UIPopoverController's dismissPopoverAnimated: both take a BOOL argument and return void, you can create the method signature using one selector, but send the invocation to another.
HI, I have a static NSMutableArray* staticArray in an NSOperation subclass (in myOperation.m) and a method:
static NSMutableArray *staticArray =
nil;
+(void) initialize {
staticArray = [[NSMutableArray alloc] init];
}
-(void) addStrToStaticArray:(NSString*)aStr {
if([staticArray indexOfObject:aStr] == NSNotFound) {
[staticArray addObject:aStr];
[staticArray performSelector:#selector(removeObject:)
withObject:aStr
afterDelay:30.];
}
}
I call the above method and after that the operation finishes execution. The problem is that aStr is never removed from the array. What am I missing ? Thanks...
Based to Justin suggestions, I can now delayed remove an object from an array invoking the method from inside a NSThread, NSOperation despite their existence at the time of the removal:
NSMethodSignature * mySignature = [NSMutableArray instanceMethodSignatureForSelector:#selector(removeObject:)];
NSInvocation * myInvocation = [NSInvocation invocationWithMethodSignature:mySignature];
[myInvocation setTarget:staticArray];
[myInvocation setSelector:#selector(removeObject:)];
[myInvocation setArgument:&aStr atIndex:2];
//At this point, myInvocation is a complete object, describing a message that can be sent.
NSTimer *timer = [NSTimer timerWithTimeInterval:90.
invocation:myInvocation
repeats:NO];
if(timer) {
NSRunLoop *mainRL = [NSRunLoop mainRunLoop];
[mainRL addTimer:timer forMode:NSDefaultRunLoopMode];
}
The aStr will be removed from staticArray after 90 seconds. For details...
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DistrObjects/Tasks/invocations.html
use timer (CFRunLoopTimer/NSTimer) and run loop (CFRunLoop/NSRunLoop) apis to accomplish this.
in that case, you create a timer, and add it to the main run loop.
this would also require that you create a function or method for your timer to call. if you choose a method, you could use an NSInvocation instead (if that's what you prefer).
since the data is static, and the operation (presumably) won't exist, you can message via a class method.
The NSObject method performSelector:withObject:afterDelay: allows me to invoke a method on the object with an object argument after a certain time. It cannot be used for methods with a non-object argument (e.g. ints, floats, structs, non-object pointers, etc.).
What is the simplest way to achieve the same thing with a method with a non-object argument? I know that for regular performSelector:withObject:, the solution is to use NSInvocation (which by the way is really complicated). But I don't know how to handle the "delay" part.
Thanks,
Here is what I used to call something I couldn't change using NSInvocation:
SEL theSelector = NSSelectorFromString(#"setOrientation:animated:");
NSInvocation *anInvocation = [NSInvocation
invocationWithMethodSignature:
[MPMoviePlayerController instanceMethodSignatureForSelector:theSelector]];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:theMovie];
UIInterfaceOrientation val = UIInterfaceOrientationPortrait;
BOOL anim = NO;
[anInvocation setArgument:&val atIndex:2];
[anInvocation setArgument:&anim atIndex:3];
[anInvocation performSelector:#selector(invoke) withObject:nil afterDelay:1];
Just wrap the float, boolean, int or similar in an NSNumber.
For structs, I don't know of a handy solution, but you could make a separate ObjC class that owns such a struct.
DO NOT USE THIS ANSWER. I HAVE ONLY LEFT IT FOR HISTORICAL PURPOSES. SEE THE COMMENTS BELOW.
There is a simple trick if it is a BOOL parameter.
Pass nil for NO and self for YES. nil is cast to the BOOL value of NO. self is cast to the BOOL value of YES.
This approach breaks down if it is anything other than a BOOL parameter.
Assuming self is a UIView.
//nil will be cast to NO when the selector is performed
[self performSelector:#selector(setHidden:) withObject:nil afterDelay:5.0];
//self will be cast to YES when the selector is performed
[self performSelector:#selector(setHidden:) withObject:self afterDelay:10.0];
Perhaps NSValue, just make sure your pointers are still valid after the delay (ie. no objects allocated on stack).
I know this is an old question but if you are building iOS SDK 4+ then you can use blocks to do this with very little effort and make it more readable:
double delayInSeconds = 2.0;
int primitiveValue = 500;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self doSomethingWithPrimitive:primitiveValue];
});
PerformSelector:WithObject always takes an object, so in order to pass arguments like int/double/float etc..... You can use something like this.
//NSNumber is an object..
[self performSelector:#selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f]
afterDelay:1.5];
-(void) setUserAlphaNumber: (NSNumber*) number{
[txtUsername setAlpha: [number floatValue] ];
}
Same way you can use [NSNumber numberWithInt:] etc.... and in the receiving method you can convert the number into your format as [number int] or [number double].
Blocks are the way to go. You can have complex parameters, type safety, and it's a lot simpler and safer than most of the old answers here. For example, you could just write:
[MONBlock performBlock:^{[obj setFrame:SOMETHING];} afterDelay:2];
Blocks allow you to capture arbitrary parameter lists, reference objects and variables.
Backing Implementation (basic):
#interface MONBlock : NSObject
+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay;
#end
#implementation MONBlock
+ (void)imp_performBlock:(void(^)())pBlock
{
pBlock();
}
+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay
{
[self performSelector:#selector(imp_performBlock:)
withObject:[pBlock copy]
afterDelay:pDelay];
}
#end
Example:
int main(int argc, const char * argv[])
{
#autoreleasepool {
__block bool didPrint = false;
int pi = 3; // close enough =p
[MONBlock performBlock:^{NSLog(#"Hello, World! pi is %i", pi); didPrint = true;} afterDelay:2];
while (!didPrint) {
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeInterval:0.1 sinceDate:NSDate.date]];
}
NSLog(#"(Bye, World!)");
}
return 0;
}
Also see Michael's answer (+1) for another example.
I would always recomend that you use NSMutableArray as the object to pass on. This is because you can then pass several objects, like the button pressed and other values. NSNumber, NSInteger and NSString are just containers of some value. Make sure that when you get the object from the array
that you refer to to a correct container type. You need to pass on NS containers. There you may test the value. Remember that containers use isEqual when values are compared.
#define DELAY_TIME 5
-(void)changePlayerGameOnes:(UIButton*)sender{
NSNumber *nextPlayer = [NSNumber numberWithInt:[gdata.currentPlayer intValue]+1 ];
NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:sender, nil];
[array addObject:nextPlayer];
[self performSelector:#selector(next:) withObject:array afterDelay:DELAY_TIME];
}
-(void)next:(NSMutableArray*)nextPlayer{
if(gdata != nil){ //if game choose next player
[self nextPlayer:[nextPlayer objectAtIndex:1] button:[nextPlayer objectAtIndex:0]];
}
}
I also wanted to do this, but with a method that receives a BOOL parameter. Wrapping the bool value with NSNumber, FAILED TO PASS THE VALUE. I have no idea why.
So I ended up doing a simple hack. I put the required parameter in another dummy function and call that function using the performSelector, where withObject = nil;
[self performSelector:#selector(dummyCaller:) withObject:nil afterDelay:5.0];
-(void)dummyCaller {
[self myFunction:YES];
}
I find that the quickest (but somewhat dirty) way to do this is by invoking objc_msgSend directly. However, it's dangerous to invoke it directly because you need to read the documentation and make sure that you're using the correct variant for the type of return value and because objc_msgSend is defined as vararg for compiler convenience but is actually implemented as fast assembly glue. Here's some code used to call a delegate method -[delegate integerDidChange:] that takes a single integer argument.
#import <objc/message.h>
SEL theSelector = #selector(integerDidChange:);
if ([self.delegate respondsToSelector:theSelector])
{
typedef void (*IntegerDidChangeFuncPtrType)(id, SEL, NSInteger);
IntegerDidChangeFuncPtrType MyFunction = (IntegerDidChangeFuncPtrType)objc_msgSend;
MyFunction(self.delegate, theSelector, theIntegerThatChanged);
}
This first saves the selector since we're going to refer to it multiple times and it would be easy to create a typo. It then verifies that the delegate actually responds to the selector - it might be an optional protocol. It then creates a function pointer type that specifies the actual signature of the selector. Keep in mind that all Objective-C messages have two hidden first arguments, the object being messaged and the selector being sent. Then we create a function pointer of the appropriate type and set it to point to the underlying objc_msgSend function. Keep in mind that if the return value is a float or struct, you need to use a different variant of objc_msgSend. Finally, send the message using the same machinery that Objective-C uses under the sheets.
You Could just use NSTimer to call a selector:
[NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(yourMethod:) userInfo:nil repeats:NO]
Calling performSelector with an NSNumber or other NSValue will not work. Instead of using the value of the NSValue/NSNumber, it will effectively cast the pointer to an int, float, or whatever and use that.
But the solution is simple and obvious. Create the NSInvocation and call
[invocation performSelector:#selector(invoke) withObject:nil afterDelay:delay]
Pehaps...ok, very likely, I'm missing something, but why not just create an object type, say NSNumber, as a container to your non-object type variable, such as CGFloat?
CGFloat myFloat = 2.0;
NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];
[self performSelector:#selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];