SEL twoArgumentSelector = #selector(methodWithTwoArguments:and:);
[newsButton addTarget:self action:#selector(twoArgumentSelector) forControlEvents:UIControlEventTouchUpInside];
-(void)methodWIthTwoArguments:(id)argumentOne and:(id)argumentTwo;
I have seen some examples that let you use two arguments in a selector. How would any do that in the above code? ty in advance.
#selector(twoArgumentSelector:and:)
although I'm not sure how you would send two arguments with a control event...
edit:
you know that the selector isn't actually calling the method, so you can't pass the arguments with the selector. It is basically just the name for a block of code (the method). Read this. A better solution would be to have the control event call a separate method which could then determine the arguments to send to the method with 2 parameters.
UIControls events by will only send a reference to themselves if their target selector allows for one argument. This is all you get. UIButton is one such UIControl subclass.
- (void)buttonAction:(id)sender; //(reference to button)
The easiest way to accomplish what you want is to make another method on your button target (in this case self) that calls out to your two argument selector.
[newsButton addTarget:self action:#selector(buttonAction:) forControlEvents:...];
- (void)buttonAction:(id)sender
{
[self methodwithTwoArguements:sender and:otherObject];
}
This could also be solved with a UIButton subclass, but depending on what your second argument needs to be, this is the simplest way.
Unless you are subclassing the object, there is no need for this because the argument to UIControlEvents is the id of the instance sending the event. You can get all the information you could possibly need from that instance.
What you're looking for is NSInvocation.
Here's an example:
SEL mySelector; // 2 parameter selector.
// ...
NSMethodSignature *signature = [[MyClass class] instanceMethodSignatureForSelector:mySelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:myTarget];
[invocation setArgument:arg1 atIndex:0];
[invocation setArgument:arg2 atIndex:1];
[invocation invoke];
This will do exactly what you want.
It's worth noting, however, that the control event you are binding to isn't going to pass anything in for a second parameter. This will only work if, by contract, the object that is calling back actually has a second parameter to pass in.
Related
I need to pass parameters with #selector and here is the method that i need to call using selector:
-(void)clickedInfo:(NSString *)itemIndex{
// some work with itemIndex
}
I know that what i can do is to use an intermediate method as described here.
This approach doesn't work in my case because im adding the target to the uibutton in the cellForItemAtIndexPath method for the collectionView.
The parameter that i need to pass to the clickedInfo method is indexPath.row
and i can not obtain this parameter in an intermediate method.
Thanx in advance
So you want to store some information that can be accessed by the action of a button. Some options are:
Use the tag property of the control. (can only store an integer)
Subclass UIButton and use that class for the button. The class can have a field that stores the information.
Use associated objects (associative references) to attach an object to the button. This is the most general solution.
You can use the performSelector:withObject: selector to pass an object.
Example:
[self performSelector:#selector(clickedInfo:) withObject:myIndex];
- (void) clickedInfo:(NSString *)itemIndex{
// some work with itemIndex
}
Edit: Should be just #selector(clickedInfo:) rather than what I had before.
Edit: Using #newacct 's suggestion, I'd recommend doing something similar to the following:
- (UITableViewCell *)tableView:(UITableView)tableView cellForRowAtIndexPath:(NSIndexPath)indexPath
{
button.tag = indexPath.row;
[button performSelector:#selector(clickedInfo:)];
// or
[button addTarget:self action:#selector(clickedInfo:) forControlEvents:UITouchUpInside];
}
- (void) clickedInfo:(id)sender
{
int row = sender.tag;
// Do stuff with the button and data
}
this is addressed lots of places, but it is easier to answer than to point you there:
[someObject performSelector:#selector(clickedInfo:) withObject:someOtherObject];
where someObject is the receiver and someOtherObject is the parameter passed to clickedInfo
I want to get the name of the arguments of the current function I am in so that I can prepare loading that object from the filesystem if it's not present on the current instance. (for instance if [foo dictTest] is not available I want to load it's prior saved plist version into exactly that ivar)
I want to find the file by providing the ivar name that I provided as an argument to the current function.
This is the function code:
-(NSDictionary*)getCachedDictionary:(NSDictionary*)dict{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:_cmd]];
NSString * firstArgument = nil;
[invocation getArgument:&firstArgument atIndex:2];
NSLog(#"Trying to get the objects ivar %#",firstArgument);
// right now during testing return nil
return nil;
}
As soon as the code reaches the NSLog I am getting a null value from firstArgument.
Why is that? Could it be possible that I would have to wait for the complete invocation of that current method I am in or is it actually better to create a proxy function that implicitly calls my class method via an invocation that eats the ivar name provided by setArgument so that I can use that argument string like I want?
Thanks a lot in advance!
P.S.: In this particular example I do not want to use KVC to identify the ivar and return it.
You've misunderstood the NSInvocation API. +[NSInvocation invocationWithMethodSignature:] creates a new NSInvocation that is keyed to accept arguments of the types defined by the method signature. It does not return an NSInvocation that corresponds to the current method invocation. This is pretty easy to see why:
- (void)doBar:(id)bip {
NSLog(#"hi there!")
}
- (void)doFoo {
NSMethodSignature *sig = [self methodSignatureForSelector:#selector(doBar:)];
NSInvocation *i = [NSInvocation invocationWithMethodSignature:sig];
}
When you create the invocation in doFoo for the doBar: method, it's obvious to see that the arguments must be empty, because doBar: hasn't been executed, and thus there is no argument. Changing #selector(doBar:) to _cmd wouldn't magically change anything.
So the next question: is there a way to get an NSInvocation for the current method invocation? Not that I know of. NSInvocation is an extremely complicated class, and constructing one from the current method would be a nightmare.
I strongly suggest finding a different approach to do whatever it is you want to do.
Even though the question is old and answered, here is a link that provides an easy and very elegant way to create an invocation instance for any selector/method that is known at compile time:
http://www.cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html
I'm searching for a nice way to perform a selector on the main thread with two parameters
I really like using
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
method, except now I have two parameters.
So basically I have a delegate which I need to notify when the image is loaded:
[delegate imageWasLoaded:(UIImage *)image fromURL:(NSString *)URLString;
But the method where I do this might be invoked in the background thread, and the delegate will use this image to update the UI, so this needs to be done in the main thread. So I really want the delegate to be notified in the main thread as well.
So I see one option - I can create a dictionary, this way I have only one object, which contains two parameters I need to pass.
NSDictionary *imageData = [NSDictionary dictionaryWithObjectsAndKeys:image, #"image", URLString, #"URLstring", nil];
[(NSObject *)delegate performSelectorOnMainThread:#selector(imageWasLoaded:) withObject: imageData waitUntilDone:NO];
But this approach does not seem right to me. Is there more elegant way to do this? Perhaps using NSInvocation?
Thanks in advance.
Using an NSDictionary to pass multiple parameters is the right way to go about it in this case.
However, a more modern method is to use GCD and blocks, this way you can send messages to an object directly. Also, it looks as if your delegate method might be doing something UI updates; which you are correctly handling on the main thread. With GCD you can do this easily, and asynchronously like this:
dispatch_async(dispatch_get_main_queue(), ^{
[delegate imageWasLoaded:yourImage fromURL:yourString;
});
Replace your performSelector:withObject call with this, and you won't have to mess around with changing your method signatures.
Make sure you:
#import <dispatch/dispatch.h>
to bring in GCD support.
Since you don't have access to GCD, NSInvocation is probably your best choice here.
NSMethodSignature *sig = [delegate methodSignatureForSelector:selector];
NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sig];
[invoke setTarget:delegate]; // argument 0
[invoke setSelector:selector]; // argument 1
[invoke setArgument:&arg1 atIndex:2]; // arguments must be stored in variables
[invoke setArgument:&arg2 atIndex:3];
[invoke retainArguments];
/* since you're sending this object to another thread, you'll need to tell it
to retain the arguments you're passing along inside it (unless you pass
waitUntilDone:YES) since this thread's autorelease pool will likely reap them
before the main thread invokes the block */
[invoke performSelectorOnMainThread:#selector(invoke) withObject:nil waitUntilDone:NO];
Following method can also be used:
- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject
As per the docs of this method-
Invokes a method of the receiver on the current thread using the default mode after a delay.
Yes, you've got the right idea: you need to encapsulate all the data you want to pass to the delegate on the main thread into one single object which gets passed along via performSelectorOnMainThread. You can pass it along as a NSDictionary object, or a NSArray object, or some custom Objective C object.
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.
I've asked a related question, but thought I'd split this out into its own question. See the code below for calling a property getter.
SEL propSelector = NSSelectorFromString(propertyName);
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[target class]instanceMethodSignatureForSelector:propSelector]];
[inv setSelector:propSelector];
[inv setTarget:target];
[inv invoke];
float value;
[inv getReturnValue:&value];
I'd like to do the same thing, but call the property SETTER. I'd also like to avoid manually crafting the setter name by building a #"setPropertyName:" string. Bottom line - is it possible to use the selector created on this line to call the setter?
SEL propSelector = NSSelectorFromString(propertyName);
Use Key-Value Coding.
I.e. [someObject setValue: anObjectValue forKey: #"foo"];
Having read your other question earlier today, this is what I can come up with:
It's not possible to use it directly as the setter. The getter does not accept any arguments, so your selector will never have the colon, so it's never gonna work.
Easiest thing to do is define your setter methods as foo: instead of setFoo:. This way you'll only have to append the colon (which is quite cheap, requires only two extra strings)