Objective C calling method dynamically with a string - iphone

Im just wondering whether there is a way to call a method where i build the name of the method on the fly with a string.
e.g. I have a method called loaddata
-(void)loadData;
to call this i would normally call it like
[self loadData];
But i want to be able to call it dynamically with a string e.g.
NSString *methodName = [[NSString alloc] initWithString:#"loadData"];
[self methodName];
This is a stupid example but i hope you get my point. I am using it for databinding classes that I am setting up for my IPad application. Hard to explain but to get it to fire I need to work out how to call a method with a string.
Any ideas?
Thanks

You can try something like
SEL s = NSSelectorFromString(selectorName);
[anObject performSelector:s];

You can use the objc_msgSend function. It takes two parameters, the receiver and the selector to send to it:
objc_msgSend(self, someSelector);
You'll need to turn your string into the appropriate selector using NSSelectorFromString:
NSString *message = [self getSomeSelectorName];
objc_msgSend(self, message);
The method also takes a variable number of arguments, so you can send messages with any number of arguments.
NSString *message = [self getSomeSelectorNameWithManyArguments];
objc_msgSend(self, message, arg1, arg2, arg3, arg4);

Related

How to call a non-void function? Xcode

How do I call a non-void function? Normal [self methodName]; works. But how do I do this for a method that returns an NSString. I keep getting an error. For example:
+ (NSString *)formulateYQLRequestFor:(NSArray *)tickers
How do I call this? [self formulateYQLRequestFor]; gives me an error.
Sorry about the formatting, for some reason safari won't let me indent.
Thanks!
+ designates a class function. You call it with the class name, not an instance.
Instead of:
[self formulateYQLRequestFor:myArray];
Do this:
[MyClassName formulateYQLRequestFor:myArray];
Alternatively, you can do this:
[[self class] formulateYQLRequestFor:myArray];
You don't have to do anything with the return value if you don't want to. At least with ARC, the return value will be automatically released. However, since it's unlikely that the function does anything on its own, you probably should do something with the return value:
NSString *returnValue = [[self class] formulateYQLRequestFor:myArray];
// Do something with returnValue
Finally, if you want to call the function without passing in an array, you still need the array parameter, but perhaps the function will accept nil for the array:
NSString *returnValue = [[self class] formulateYQLRequestFor:nil];
There are two problems with your call to [self formulateYQLRequestFor];
Firstly, the method takes a parameter, which you haven't provided. Because of this, the compiler is looking for the method called formulateYQLRequestFor instead of formulateYQLRequestFor: This is significant, because the : is part of the method name in Objective-C. So you are trying to call a method that doesn't exist.
Secondly, self is sending a message to an instance of your class. The + in the method signature indicates that you have a class method, and so self does not respond to the method you are trying to call.
The correct way to do this is:
NSString *resultString = [[self class] formulateYQLRequestFor:someArray];
where someArray is a valid NSArray parameter.
I don't know what - (NSString *)formulateYQLRequestFor: does with the NSArray, but if it isn't necessary you can just call [self formulateYQLRequestFor:nil];. Alternatively you can call it with an empty array [self formulateYQLRequestFor:[NSArray array]];.

Calling a method in another method?

I've got a delegate method which just has a bit of code in there that puts a % sign on the end of the number entered.
- (void)textFieldDidEndEditing:(UITextField *)UItextfield {
NSString *oldTextFieldValue = UItextfield.text;
UItextfield.text = [NSString stringWithFormat:#"%# %%",oldTextFieldValue];
}
Could I instead of having that, have the following action
-(IBAction)Calculate:(UITextField *)UITextfield;
{
NSString *oldTextFieldValue = UItextfield.text;
UItextfield.text = [NSString stringWithFormat:#"%# %%",oldTextFieldValue];
}
And then in the Delegate function, call that action? Something like
-(void)textFieldDidEndEditing:(UITextField *)UItextfield {
[self Calculate:self]
}
I tried that, it doesn't work. I know it'll get me to the same result but I just want to know if it can be done. I think i'm asking can a method (Calculate) be called in another method (textFieldDidEndEditing) and how.
You are providing self as the method argument which is the instance of the class you are in. Which in this case is wrong since the method argument should be an instance of UITextField. Try instead [self Calculate:UItextfield] in your method.
Calling other methods from methods happen all the time in most programming languages.
It's a great way to split code up and reuse code in different places without having to copy/paste.
(This might be too basic for you. Sorry in that case)
Things may be easier to understand if you use standard naming conventions too. ('likeThis' for variables and method names; 'LikeThis' for class names)
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSString *oldTextFieldValue = textField.text;
textField.text = [NSString stringWithFormat:#"%# %%",oldTextFieldValue];
}
textField here, is the pointer to the UITextField object which just finished editing.
You want to pass this object to your new 'other' method.
[self calculate:textField];
self is a pointer to an instance of the current class. For example, in a UIViewController subclass called 'MyViewController', self refers to the current instance of this class.
Since the -calculate method is an instance method (beginning with a '-') it requires you to use self. The variable textField is passed after the colon.
- (void)calculate: (UITextField*)textField {
NSString *oldTextFieldValue = textField.text;
textField.text = [NSString stringWithFormat:#"%# %%",oldTextFieldValue];
}
Use only the IBAction keyword when you want the method to be called from a UIComponent in an xib or storyboard.

custom setter for NSString

I have a NSString called fontType
and I am trying to have a custom setter for it:
- (void) setFontType:(NSString *) fType
{
if (self.fontType != fType){
[fontType release];
self.fontType = [fType retain];
//some more custom code
}
}
Is there any issue with this?
A few things that stand out for me:
do not use self. inside of custom accessors. access the variable directly
it's better use copy semantics for properties of a type that has a
mutable subtype
be careful with whatever is // some more custom code
My personal style preferences are like so:
-(void)setFontType:(NSString *)fontType_ {
if (fontType == fontType_) return; // get out quick, flatten the code
[fontType release];
fontType = [fontType_ copy];
// some more code
}
Cocoa with Love has a good article on this topic. It's worth a read.
When you do self.fontType = newFontType, you are doing [self setFontType:newFontType] (just with a different syntax), this means you are calling the method inside itself.
This is called recursion, but in this case, you don't have a base case in which the method will stop calling itself, so my guess is that this method will call itself until the app crashes. In short, recursion is not something you want here.
Just replace self.fontType = [fType retain] with fontType = [fType retain] (Assuming the var linked to the fontType property is called fontType as well).
PS. At the end of the question you asked
Is there any issue with this?
If you didn't try this, then you shouldn't even be asking that here on StackOverflow, and if you did try it, then you should have realized that this method didn't work, so that last line is pretty meaningless. Just saying.

How do I call methods in a class that I created dynamically with NSClassFromString?

The reason I am doing dynamic class loading is because I am creating a single set of files that can be used across multiple similar projects, so doing a #import and then normal instantiation just won't work. Dynamic classes allows me to do this, as long as I can call methods within those classes. Each project has this in the pch with a different "kMediaClassName" name so I can dynamically load different classes based on the project I'm in:
#define kMediaClassName #"Movie"
Here is the code I am using to get an instance of a class dynamically:
Class mediaClass = NSClassFromString(kMediaClassName);
id mediaObject = [[[mediaClass alloc] init] autorelease];
Then I try to call a method within that dynamic class:
[mediaObject doSomething];
When I then type this into Xcode, the compiler shows a warning that the class doesn't have this method, even though it does. I can see it right there in my Movie.h file. What is going on? How do I call a method from a dynamically instantiated class?
And what if I need to pass multiple arguments?
[mediaObject loadMedia:oneObject moveThe:YES moveA:NO];
Thanks for the help in advance.
you can declare a protocol, like so:
#protocol MONMediaProtocol
/*
remember: when synthesizing the class, you may want
to add the protocol to the synthesized class for your sanity
*/
- (BOOL)downloadMediaAtURL:(NSURL *)url toPath:(NSString *)path loadIfSuccessful:(BOOL)loadIfSuccessful;
/* ...the interface continues... */
#end
in use:
Class mediaClass = NSClassFromString(kMediaClassName);
assert(mediaClass);
id<MONMediaProtocol> mediaObject = [[[mediaClass alloc] init] autorelease];
assert(mediaObject);
NSURL * url = /* expr */;
NSString * path = /* expr */;
BOOL loadIfSuccessful = YES;
BOOL success = [mediaObject downloadMediaAtURL:url toPath:path loadIfSuccessful:loadIfSuccessful];
Well it might be there, but the Compiler doesn't know about it because it assumes that mediaClass is just some Class object, but nothing specific. NSClassFromString() is a runtime function and thus can't give the compiler a hint at compile time about the object.
What you can do:
Ignore the warning
Use [media performSelector:#selector(doSomething)];
And btw, this is wrong:
Class mediaClass; = NSClassFromString(kMediaClassName);
it should be:
Class mediaClass = NSClassFromString(kMediaClassName);
An easier and fancier solution than NSInvocation :)
Class mediaClass = NSClassFromString(kMediaClassName);
if(mediaClass){
id mediaObject = class_createInstance(mediaClass,0);
objc_msgSend(mediaObject, #selector(doSomethingWith:andWith:alsoWith:), firstP, secondP,thirdP);
}
Explanation:
class_createInstance(mediaClass,0); does exactly the same as [[mediaClass alloc] init];
if you need to autorelease it, just do the usual [mediaObject autorelease];
objc_msgSend() does exactly the same as performSelector: method but objc_msgSend() allows you to put as many parameters as you want. So, easier than NSInvocation right? BTW, their signature are:
id class_createInstance(Class cls, size_t extraBytes)
id objc_msgSend(id theReceiver, SEL theSelector, ...)
For more info you can refer the Objective-C Runtime Reference
As Joe Blow says, NSInvocation will help you here, though NSObject has a couple of shortcut methods that you can use: -performSelector:, -performSelector:withObject:, and -performSelector:withObject:withObject:.

NSInvocation making app crash

I'm using NSInvocation as follows:
In my init, I'm writing this in my viewDidLoad:
SEL mySelector;
mySelector = #selector(initParsersetId:type:);
NSMethodSignature * sig = nil;
sig = [[self class] instanceMethodSignatureForSelector:mySelector];
myInvocation = nil;
myInvocation = [NSInvocation invocationWithMethodSignature:sig];
[myInvocation setTarget:self];
[myInvocation setSelector:mySelector];
And I'm calling it like this:
Idea *tempIdea = [[Idea alloc]init];
tempIdea = [genericArray objectAtIndex:indexPath.row];
idea.ideaId = tempIdea.ideaId;
[tempIdea release];
NSNumber *_id_ = [NSNumber numberWithInt:idea.ideaId];
[myInvocation setArgument:_id_ atIndex:2]; //CRASHING AT THIS LINE
My application is crashing at the indicated line. Can anybody please help me?
It is not very clear from your codes; however, I see something suspicious. Hopefully, it may provide your some helpful hints.
First, I don't see you retain the instance (auto released from [NSInvocation...). Since the instance from [NSInvocation...] is auto-released, your class level variable myInvocation would not retain it after viewDidLoad event.
Second thing in your codes is that the selector is a kind of customized constructor, beginning with init..... I am not sure if you can invoke the event within the same instance. Another point is that if your init... method to be invoked returns self? It should be.
You may output some messages in your selector event by using NSLog function. All the messages by NSLog will be in your XCode's output console.
I've found the answer but I'm not convinced how. Actually, initially I was writing all the initialisation code in viewDidLoad and simply reusing the NSInvocation object by passing it the different argument since NSInvocation is a mutable object. It didn't work. Then I wrote a method with all the initialisation code inside it and called that method every time I used the NSInvocation object and it worked...
You need to give setArgument: the address of the argument you are passing, and not the argument itself:
[myInvocation setArgument:&_id_ atIndex:2];
NOT
[myInvocation setArgument:_id_ atIndex:2];
Also, are you sure your function takes an NSNumber as first argument?