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?
Related
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]];.
I'm doing some work with dynamic programming in Objective-C and I've read through the Objective-C Runtime Programming Guide front to back and been able to do most of what I need, but the one thing I haven't figured out is how to call a method dynamically provided I have a string representation of it.
Essentially I dynamically do a property lookup to see if my object has a property that matches from a list using class_copyPropertyList and then loop through and match these via an NSMutableDictionary that is populated from a plist file. When a match is found I want to execute the property. I have no way of knowing ahead of time what matches could possibly exist as this is a lib that will be packaged into many different apps.
Use NSSelectorFromString to create a SEL from an NSString. Then you can execute it using one of the performSelector methods.
Setting a property dynamically:
SEL setter = NSSelectorFromString(#"setProperty:");
[myObject performSelector:setter withObject:newValue];
Getting a property dynamically:
SEL getter = NSSelectorFromString(#"property");
id myProperty = [myObject performSelector:getter];
For more complicated methods you can use NSInvocation and NSMethodSignature:
SEL action = NSSelectorFromString(#"someMethod:withArguments:");
NSMethodSignature *signature = [myObject methodSignatureForSelector:action];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:arg1 atIndex:2]; // indices 0 and 1 are reserved.
[invocation setArgument:arg2 atIndex:3];
[invocation invokeWithTarget:myObject];
id returnedObject;
[invocation1 getReturnValue:&returnedObject];
SEL s = NSSelectorFromString(selectorName);
[anObject performSelector:s];
Apple docs: https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSelectorFromString
I've followed a few tutorials and I cant seem to adapt them to my needs.
Simply I have a manager objects that returns a NSInvocation to be stored and invoked at a later point in the app.
and when I run the application, my method signature is null and even trying to assign a NSInvocation from a getter is causing a SIGABRT.
#interface Cars : NSObject
+ (NSArray *)all;
#end
cars all method just returns an array "1","2","3","4".
and in my object manager I do this:
- (NSInvocation *) cars_ALL {
NSMethodSignature *ca = [Cars instanceMethodSignatureForSelector:#selector(all)];
NSLog(#"%#", ca);
return [NSInvocation invocationWithMethodSignature:ca];
}
and I call it like this:
NSInvocation *cinv = [myObjectManager cars_ALL];
thats it, im not even up to invoking or assigning the target etc of the invocation.
anything I try just doesn't work out.
I'm new to cocoa / objective-c and i'm struggeling with the releases of my objects. I have the following code:
gastroCategoryList = [[NSMutableArray alloc] init];
for (NSDictionary *gastrocategory in gastrocategories) {
NSString *oid = [gastrocategory objectForKey:#"id"];
GastroCategory *gc = [[GastroCategory alloc] initWithId:[oid intValue] name:[gastrocategory objectForKey:#"name"]];
[gastroCategoryList addObject:gc];
}
The analyzer shows me that the "gastrocategory" defined in the for is a potential memory leak. But i'm not sure if i can release this at the end of the for loop?
Also at the following code:
- (NSArray *)eventsForStage:(int)stageId {
NSMutableArray *result = [[NSMutableArray alloc] init];
for (Event *e in eventList) {
if ([e stageId] == stageId) {
[result addObject:e];
}
}
return result;
}
The Analyzer tells me that my "result" is a potential leak. But where should I release this?
Is there also a simple rule to memorize when i should use assign, copy, retain etc. at the #property ?
Another problem:
- (IBAction)showHungryView:(id)sender {
GastroCategoriesView *gastroCategoriesView = [[GastroCategoriesView alloc] initWithNibName:#"GastroCategoriesView" bundle:nil];
[gastroCategoriesView setDataManager:dataManager];
UIView *currentView = [self view];
UIView *window = [currentView superview];
UIView *gastroView = [gastroCategoriesView view];
[window addSubview:gastroView];
CGRect pageFrame = currentView.frame;
CGFloat pageWidth = pageFrame.size.width;
gastroView.frame = CGRectOffset(pageFrame,pageWidth,0);
[UIView beginAnimations:nil context:NULL];
currentView.frame = CGRectOffset(pageFrame,-pageWidth,0);
gastroView.frame = pageFrame;
[UIView commitAnimations];
//[gastroCategoriesView release];
}
I don't get it, the "gastroCategoriesView" is a potential leak. I tried to release it at the end or with autorelease but neither works fine. Everytime I call the method my app is terminating. Thank you very much again!
In your loop, release each gc after adding it to the list since you won't need it in your loop scope anymore:
gastroCategoryList = [[NSMutableArray alloc] init];
for (NSDictionary *gastrocategory in gastrocategories) {
NSString *oid = [gastrocategory objectForKey:#"id"];
GastroCategory *gc = [[GastroCategory alloc] initWithId:[oid intValue] name:[gastrocategory objectForKey:#"name"]];
[gastroCategoryList addObject:gc];
[gc release];
}
In your method, declare result to be autoreleased to absolve ownership of it from your method:
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
// An alternative to the above, produces an empty autoreleased array
NSMutableArray *result = [NSMutableArray array];
EDIT: in your third issue, you can't release your view controller because its view is being used by the window. Setting it to autorelease also causes the same fate, only delayed.
You'll have to retain your GastroCategoriesView controller somewhere, e.g. in an instance variable of your app delegate.
BoltClock's answer is spot-on as to the first part of your question. I'll try to tackle the rest.
Assign is for simple, non-object types such as int, double, or struct. It generates a setter that does a plain old assignment, as in "foo = newFoo". Copy & retain will, as their names imply, either make a copy of the new value ("foo = [newFoo copy]") or retain it ("foo = [newFoo retain]"). In both cases, the setter will release the old value as appropriate.
So the question is, when to copy and when to retain. The answer is... it depends. How does your class use the new value? Will your class break if some other code modifies the incoming object? Say, for example, you have an NSString* property imaginatively named "theString." Other code can assign an NSMutableString instance to theString - that's legal, because it's an NSString subclass. But that other code might also keep its own reference to the mutable string object, and change its value - is your code prepared to deal with that possibility? If not, it should make its own copy, which the other code can't change.
On the other hand, if your own code makes no assumptions about whether theString might have been changed, and works just as well whether or not it was, then you'd save memory by retaining the incoming object instead of unnecessarily making a copy of it.
Basically, the rule, which is unfortunately not so simple sometimes, is to think carefully about whether your own code needs its own private copy, or can correctly deal with a shared object whose value might be changed by other code.
The reason you can release gc after it is added to the gastroCategoryList is that when an object is added to an array, the array retains that object. So, even though you release your gc, it will still be around; retained by the gastroCategoryList.
When you are returning a newly created object from a method, you need to call autorelease. This will cause the object to be released only after the runtime leaves the scope of the calling method, thereby giving the calling method a chance to do something with the returned value.
Note that if your method starts with the word copy or new, then you should not autorelease your object; you should leave it for the calling method to release.
As for copy vs retain vs assign... as a general rule, copy objects that have a mutable version, such as NSArray, NSSet, NSDictionary, and NSString. This will ensure that the object you have a pointer to is not mutable when you don't want it to be.
Otherwise, use retain whenever you want your class to be ensured that an object is still in memory. This will apply to almost every object except for objects that are considered parents of your object, in which case you would use assign. (See the section on retain cycles here).
Also note that you have to use assign for non-object types such as int.
Read through the Memory Management Programming Guide a bit; it's quite helpful.
I want to invoke a selector of a method that has the usual NSError** argument:
-(int) getItemsSince:(NSDate *)when dataSelector:(SEL)getDataSelector error:(NSError**)outError {
NSArray *data = nil;
if([service respondsToSelector:getDataSelector]) {
data = [service performSelector:getDataSelector withObject:when withObject:outError];
// etc.
... which the compiler doesn't like:
warning: passing argument 3 of 'performSelector:withObject:withObject:' from incompatible pointer type
Is there any way around this short of encapsulating the pointer in an object?
Check out NSInvocation, which lets you "performSelector" in a much more flexible way.
Here's some code to get you started:
if ([service respondsToSelector:getDataSelector]) {
NSArray *data;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[service methodSignatureForSelector:getDataSelector]];
[invocation setTarget:delegate];
[invocation setSelector:getDataSelector];
// Note: Indexes 0 and 1 correspond to the implicit arguments self and _cmd,
// which are set using setTarget and setSelector.
[invocation setArgument:when atIndex:2];
[invocation setArgument:outError atIndex:3];
[invocation invoke];
[invocation getReturnValue:&data];
}
NSError** is not an object (id), which performSelector wants for each of the withObject args. You could go to NSInvocation, but that seems like a lot of work if this is just a single message you want to use. Try defining an intermediate selector method that takes as an arg your wrapped NSError** in an object, and let that method do the performSelector on it (which I think was probably what you meant at the end of your question?)
I'm not positive, but you may want to take a look at using an NSInvocation instead of -performSelector:withObject:withObject. Since NSInvocation takes arguments of type void*, it may/should let you set whatever you want as an argument.
It'll require several more lines of code than a simple performSelector: call, but it'll probably be more convenient than wrapping your pointer in an object.