It seems like there should be an easy way to call a selector with some arguments when all you have is a SEL object. I can't seem to find the correct syntax.
-(MyClass*) init: (SEL)sel owner:(NSObject*) parent
{
int i =10;
[parent performSelector:sel:i ];
}
Take a look at the NSObject documentation. In this case:
[parent performSelector:sel withObject:[NSNumber numberWithInt:i]];
(note this method is actually listed in the NSObject protocol documentation). Since -[NSObject performSelector:withObject:] requires an object argument, you will have to write a wrapper in parent's class like
-(void)myMethodForNumber:(NSNumber*)number {
[self myMethod:[number intValue]];
}
to unbox the NSNumber.
If you really want to invoke a method that takes non-object arguments directly (for example, you don't have control of the callee source and don't want to add a category), you can use NSInvocation:
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[parent methodSignatureForSelector:sel]];
[inv setSelector:sel];
[inv setTarget:parent];
[inv setArgument:&i atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
[inv invoke];
On a side note, your method looks like an init method, but does not follow the correct initializer pattern for Objective-C. You need to call the super-classes initializer, and you need to test for a nil result from that call and you must return self from the initializer method. In all cases, your Objective-C initializer methods should look like:
-(id)myInitMethod {
self = [super init];
if(self != nil) {
//perform initialization of self
}
return self;
}
Your method (if it's an init method) would then look like:
-(id) init: (SEL)sel owner:(NSObject*) parent
{
self = [super init];
if(self != nil) {
int i = 10;
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[parent methodSignatureForSelector:sel]];
[inv setSelector:sel];
[inv setTarget:parent];
[inv setArgument:&i atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
[inv invoke];
}
return self;
}
To be more Objective-C stylistically, I would rename the initializer -(id)initWithSelector:owner: as well.
what Barry Wark said is great.. i have just modified the discussion for lazy programmers
-(void)myMethodWith:(int)number andBOOL:(BOOL) someBool andStr:(NSString *)str{
NSLog(#"%d %d %#",number,someBool,str);
}
-(void) testMethod{
SEL sel = #selector(myMethodWith:andBOOL:andStr:);
int i = 10;
BOOL bol = YES;
NSString *str = #"hey baby !";
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:sel]];
[inv setSelector:sel];
[inv setTarget:self];
[inv setArgument:&i atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
[inv setArgument:&bol atIndex:3];
[inv setArgument:&str atIndex:4];
[inv invoke];
}
You want to use performSelector:withObject: The tricky part is converting the int to an NSObject. You cannot use performSelector with messages that take int params, it must instead take an id.
From the NSObject Protocol Reference:
aSelector should identify a method that takes a single argument of type id. For methods with other argument types and return values, use NSInvocation.
Once that change is made, you can do:
id arg = [NSNumber numberWithInt:10];
[parent performSelector:sel withObject:arg];
You can use:
- (id)performSelector:(SEL)aSelector withObject:(id)anObject
- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject
Or if you need to use more complex method use NSInvocation class
For methods that take one or two objects of type id as arguments, you can use:
[parent performSelector:sel withObject:argument1];
or
[parent performSelector:sel withObject:argument1 withObject:argument2];
For methods with other argument types, create an NSInvocation that can encapsulate arbitrary method calls.
Related
I've generated a list of methods and properties of a class using the ObjC runtime, so that those can be called later from a bridge using NSInvocation.
The problem is that for those methods that the runtime can't generate a signature I'm getting an error.
For instance calling the property getter for direction in an instance of SKFieldNode throw the exception NSInvalidArgumentException, I’m guessing that’s because vector_float3 has no encoded type, its type is '' (i.e. no character type)
This is how to test what I'm describing:
Method method = class_getInstanceMethod([SKFieldNode class], #selector(direction));
const char *types = method_getTypeEncoding(method);
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:types];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
SKFieldNode *field = [SKFieldNode node];
field.direction = (vector_float3){1,2,3};
[inv setTarget:field];
[inv setSelector:#selector(direction)]; // (*)
[inv invoke];
vector_float3 v;
[inv getReturnValue:&v];
NSLog(#"%f %f %f", v.x, v.y, v.z);
(*) "NSInvalidArgumentException", "-[NSInvocation
setArgument:atIndex:]: index (1) out of bounds [-1, 0]"
How can I tell, using introspection, whether a method can be safely called in that way?
I tried testing the number of arguments that NSMethodSignature returns, but the value is wrong for a method with missing encoded types, for instance this two methods will return 2, counting the target and selector, so that the remaining arguments are not taken into account.
- setDirection:(vector_float3)d1 direction:(vector_float3)d2;
- setDirection:(vector_float3)d;
I’ve also noticed that the direction property is not available in Swift
That make me think it’s because of this very same reason. So I wouldn't mind to drop support for those methods either in the custom bridge.
This is a fairly simple check to make sure you don't have any improperly encoded arguments:
BOOL isMethodSignatureValidForSelector(NSMethodSignature *signature, SEL selector) {
// This could break if you use a unicode selector name, so please don't do that :)
const char *c_str = sel_getName(selector);
unsigned numberOfArgs = 2;
while (*c_str) {
if (*c_str == ':') {
numberOfArgs++;
};
c_str++;
}
return ([signature numberOfArguments] == numberOfArgs);
}
You can use a try-catch statement to try and run some code:
#try {
Method method = class_getInstanceMethod([SKFieldNode class], #selector(direction));
const char *types = method_getTypeEncoding(method);
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:types];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
SKFieldNode *field = [SKFieldNode node];
field.direction = (vector_float3){1,2,3};
[inv setTarget:field];
[inv setSelector:#selector(direction)]; // (*)
[inv invoke];
vector_float3 v;
[inv getReturnValue:&v];
NSLog(#"%f %f %f", v.x, v.y, v.z);
} #catch (NSException *exception) {
}
This is my code:
[delegate performSelectorOnMainThread:#selector(setVariablePremierAffichage:) withObject:TRUE waitUntilDone:NO];
The problem is that the argument "withObject" only takes an "id" type, so, how can I cast my value "TRUE" to an id type? I also use ARC memory management in Xcode for iOS 5.
Pass an NSNumber. Use boolNumber = [NSNumber numberWithBool:TRUE]. Your method should be defined as:
-(void)setVariablePremierAffichage:(NSNumber *)boolNumber
{
BOOL value = [boolNumber boolValue];
// do something
}
Use CFbooleanreference and cast it
[delegate performSelectorOnMainThread:#selector(setVariablePremierAffichage:) withObject:(id)kCFBooleanTrue waitUntilDone:NO];
There is no way to cast a primitive to an id. If you need to call a method dynamically such as with performSelector you will need to use NSInvocation:
NSMethodSignature *sig = [self methodSignatureForSelector:#selector(setVariablePremierAffichage:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
BOOL yes = YES;
[invocation setArgument:&yes atIndex:2];
[invocation setTarget:self];
[invocation setSelector:#selector(setVariablePremierAffichage:)];
[invocation invoke];
Cheers!
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
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:¤tColor 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.
I am dealing with an old iPhone OS 2.x project and I want to keep compatibility, while designing for 3.x.
I am using NSInvocation, is a code like this
NSInvocation* invoc = [NSInvocation invocationWithMethodSignature:
[cell methodSignatureForSelector:
#selector(initWithStyle:reuseIdentifier:)]];
[invoc setTarget:cell];
[invoc setSelector:#selector(initWithStyle:reuseIdentifier:)];
int arg2 = UITableViewCellStyleDefault; //????
[invoc setArgument:&arg2 atIndex:2];
[invoc setArgument:&identificadorNormal atIndex:3];
[invoc invoke];
to have a code in a way that both 3.0 and 2.0 like, each one using its proper syntax.
I am having a problem on the line I marked with question marks.
The problem there is that I am trying to assign to arg2, a constant that has not been defined in OS 2.0. As everything with NSInvocation is to do stuff indirectly to avoid compiler errors, how do I set this constant to a variable in an indirect way? Some sort of performSelector "assign value to variable"...
is that possible? thanks for any help.
UITableViewCellStyleDefault is defined as 0 so you can use 0 wherever you would normally use UITableViewCellStyleDefault. Also, there is no need to use an NSInvocation, this will do:
UITableViewCell *cell = [UITableViewCell alloc];
if ([cell respondsToSelector:#selector(initWithStyle:reuseIdentifier:)])
cell = [(id)cell initWithStyle:0 reuseIdentifier:reuseIdentifier];
else
cell = [cell initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier];
-[UITableViewCell initWithFrame:reuseIdentifier:] will still work on 3.x, it's just deprecated.
NSInvocation* invoc = [NSInvocation invocationWithMethodSignature:
[cell methodSignatureForSelector:
#selector(initWithStyle:reuseIdentifier:)]];
[invoc setTarget:cell];
[invoc setSelector:#selector(initWithStyle:reuseIdentifier:)];
int arg2;
#if (__IPHONE_3_0)
arg2 = UITableViewCellStyleDefault;
#else
//add 2.0 related constant here
#endif
[invoc setArgument:&arg2 atIndex:2];
[invoc setArgument:&identificadorNormal atIndex:3];
[invoc invoke];
#if (__IPHONE_3_0)
arg2 = UITableViewCellStyleDefault;
#endif