I want to add selectors to an NSMutableArray. But since they're opaque types and no objects, that wouldn't work, right? Is there an wrapper object I can use? Or do I have to create my own?
You can wrap it in an NSValue instance as follows:
SEL mySelector = #selector(performSomething:);
NSValue *value = [NSValue value:&mySelector withObjCType:#encode(SEL)];
and then add value to your NSMutableArray instance.
You can store the NSString name of the selector in the array and use
SEL mySelector = NSSelectorFromString([selectorArray objectAtIndex:0]);
to generate the selector from the stored string.
Additionally, you can package up the selector as an NSInvocation using something like
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:mySelector]];
[invocation setTarget:self];
[invocation setSelector:mySelector];
[invocation setArgument:&arg atIndex:2];
[invocation retainArguments];
This NSInvocation object can then be stored in the array and invoked later.
NSValue valueWithPointer / pointerValue works equally well.
you just need to know that you can't serialise the array (ie write it to a file) if you want to do that, use the NSStringFromSelector approach.
these are all valid ways of putting a selector into an NSValue object:
id selWrapper1 = [NSValue valueWithPointer:_cmd];
id selWrapper2 = [NSValue valueWithPointer:#selector(viewDidLoad)];
id selWrapper3 = [NSValue valueWithPointer:#selector(setObject:forKey:)];
NSString *myProperty = #"frame";
NSString *propertySetter = [NSString stringWithFormat:#"set%#%#:",
[[myProperty substringToIndex:1]uppercaseString],
[myProperty substringFromIndex:1]];
id selWrapper4 = [NSValue valueWithPointer:NSSelectorFromString(propertySetter)];
NSArray *array = [NSArray arrayWithObjects:
selWrapper1,
selWrapper2,
selWrapper3,
selWrapper4, nil];
SEL theCmd1 = [[array objectAtIndex:0] pointerValue];
SEL theCmd2 = [[array objectAtIndex:1] pointerValue];
SEL theCmd3 = [[array objectAtIndex:2] pointerValue];
SEL theCmd4 = [[array objectAtIndex:3] pointerValue];
Related
I created an action in
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
which just like this:
self.moveAction = [CCSequence actions:
[CCMoveTo actionWithDuration:moveDuration position:touchLocation],
[CCCallFunc actionWithTarget:self selector:#selector(guyMoveEnded)],
nil
];
but now, I want to auto-invoke a following method by #selector:
-(void)guyMoveEnded:(BOOL)flag AndWhere:(CGPoint)where Andtime:(float)time{
//do something...
}
How can I do it?
Please help me, I'm confused about the selector.
Thanx!
You could add your parameters to a NSArray and then give it as the argument to your selector:
NSArray *params = [NSArray arrayWithObjects:#"a str", [NSNumber numberWithInt:42],myObj];
[self performSelector:#selector(myMethod:)withObject:params];
And then unpack the arguments in your method:
-(void)myMethode:(NSArray*)params
{
NSString *strArg = [params objectAtIndex:0];
NSNumber * numVal = [params objectAtIndex:1];
NSObject *objArg = [params objectAtIndex:2];
int intArg = [numVal intValue];
.
.
.
}
Apple suggests using NSInvocation when you need to pass more than 2 arguments.
Here's a very good example: NSInvocation for Dummies?
A short synopsis from that answer with no explanations:
// Prepare the object
NSMethodSignature * mySignature = [NSMutableArray
instanceMethodSignatureForSelector:#selector(addObject:)];
NSInvocation * myInvocation = [NSInvocation
invocationWithMethodSignature:mySignature];
// Set selector and object
[myInvocation setTarget:myArray];
[myInvocation setSelector:#selector(addObject:)];
// Set arguments
[myInvocation setArgument:&myString atIndex:2];
// Invoke it
[myInvocation invoke];
It is a lot more code than just calling a selector with performSelector:, but allows you to call methods with any number of arguments. This is especially useful if you can't change the signature to decrease argument count, as Cipramill suggests.
Prepare a NSDictionary object with your Parameters & pass that Dictionary to your selector & you can get the values inside your method from Dictionary. following is for reference:
"NSDictionary *dictionary = nil;
BOOL flag = YES;
CGFloat time;
CGPoint pt;
[dictionary setValue:flag forKey:FIRSTPARAM];
[dictionary setValue:time forKey:SECONDPARAM];
[dictionary setObject:point forKey:THIRDPARAM];
[self performSelector:#selector(methodName:) withObject:dictionary];
"
Either You can create your custom class for that Pass the object of that class.
[favProductCell.btnAddtoCart addTarget:self action:#selector(arrayForAddToCart:) forControlEvents:UIControlEventTouchUpInside];
[favProductCell.btnAddtoCart setTag:indexPath.row];
-(void)arrayForAddToCart:(id)sender
{
if (![self.addToCartProductIdArray containsObject:self.favoriteProductArray[[sender tag]]])
[self.addToCartProductIdArray addObject:self.favoriteProductArray[[sender tag]]];
}
I have the following code:
.h
NSString *mainString;
.m
case 0:
case 1:
case 2:
if ([typeTo textAlignment] == UITextAlignmentRight) {
typeTo.text = [NSString stringWithFormat:#""];
mainString = #"";
[typeTo setTextAlignment:UITextAlignmentLeft];
typeTo.text = [NSString stringWithFormat:#"%#%d", typeTo.text, [sender tag]];
mainString = [NSString stringWithFormat:#"%#%d", mainString, [sender tag]];
} else {
typeTo.text = [NSString stringWithFormat:#"%#%d", typeTo.text, [sender tag]];
mainString = [NSString stringWithFormat:#"%#%d", mainString, [sender tag]];
}
NSLog(#"%#",mainString);
break;
Crashes on this line usually.
mainString = [NSString stringWithFormat:#"%#%d", mainString, [sender tag]];
Code works one then crashes.
both typeTo.text and mainString start as #""
And text alignment starts left.
What am I doing wrong?
If you are not using ARC, then you need to either retain the created string or create it with alloc. So either:
mainString = [[NSString stringWithFormat:#"%#%d", mainString, [sender tag]] retain];
or better yet:
mainString = [[NSString alloc] initWithFormat:#"%#%d", mainString, [sender tag]];
This of course means you also need to release it before assigning a new value.
The reason for the crash is likely because you assign the autorelease instance to the pointer, then the object gets autoreleased but the pointer still points to that now-dead object.
Another way would be to use a property with retain or copy keyword. For strings, copy is usually the better solution because you could accidentally pass a NSMutableString and then later modify it.
Edit to answer comments:
In this case, to avoid a memory leak, the following should be done:
[mainString autorelease];
mainString = [[NSString alloc] initWithFormat:#"%#%d", mainString, [sender tag]];
The reason why this is necessary is because the mainString is used as an argument to create a new object, which is then in turn assigned to mainString. So before the initWithFormat: line, mainString pointed to a string object A. After that line, it now points to a new string object B. But you need to make sure to clean up A, which is why the autorelease is necessary. If you don't you'd have a memory leak and eventually your app will run out of memory.
Alternatively, you could also do:
NSString *tmp = mainString;
mainString = [[NSString alloc] initWithFormat:#"%#%d", tmp, [sender tag]];
[tmp release];
The difference is that autorelease says: I need this object for a short while, but some time after I leave this method it must be cleaned up if possible. release says: I don't need the object any more, please clean it up now if possible.
I get an error when running my code. The culprit is me accessing a string from a plist below:
NSString *sImageFile = [dictionary objectForKey:#"answerCorrect"];
NSLog(#"%#",sImageFile);
I have this in my cocos2d Init shown here:
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self=[super init])) {
NSUserDefaults *sud = [NSUserDefaults standardUserDefaults];
NSString *ctlLang = [sud valueForKey:#"ctlLang"];
NSNumber *questionState = [sud valueForKey:#"questionState"];
NSNumber *scoreState = [sud valueForKey:#"scoreState"];
gameArray = (NSMutableArray *)[sud valueForKey:#"gameArray"];
for (NSString *element in gameArray) {
NSLog(#"\nQL gameArray value=%d\n", [element intValue]);
}
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:ctlLang];
dictionary = [NSDictionary dictionaryWithContentsOfFile:finalPath];
NSString *sImageFile = [dictionary objectForKey:#"answerCorrect"];
NSLog(#"%#",sImageFile);
}
}
The printing of the string works fine in the init section of the scene. The problem occurs in a method I define later. For some reason it is not returning the string in the method shown here:
-(void) checkAnswer: (id) sender {
CGSize size = [[CCDirector sharedDirector] winSize];
CCMenuItemSprite *sAnswer = (CCMenuItemSprite *)sender;
NSLog(#"Checking Answer Tag is ---> %d",sAnswer.tag);
NSString *sImageFile = [dictionary objectForKey:#"answerCorrect"];
NSLog(#"%#",sImageFile);
if ([question.answer integerValue] == sAnswer.tag) {
//...
}
}
What am I missing here? the program bombs at the NSLog statement.
You assign the object returned by dictionaryWithContentsOfFile: to the dictionary ivar but you do not claim ownership of it by sending it a retain message to it:
dictionary = [NSDictionary dictionaryWithContentsOfFile:finalPath];
The method dictionaryWithContentsOfFile: returns an object you do not own. Probably, by the time checkAnswer: is executed the object has already been deallocated. You need to retain it:
dictionary = [[NSDictionary dictionaryWithContentsOfFile:finalPath] retain];
Or use alloc-initWithContentsOfFile: instead, which returns an object you own:
dictionary = [[NSDictionary alloc] initWithContentsOfFile:finalPath];
And the same goes for the gameplay ivar. You do not own the object returned by valueForKey: and you need to retain it. So this line:
gameArray = (NSMutableArray *)[sud valueForKey:#"gameArray"];
should be:
gameArray = [[sud valueForKey:#"gameArray"] retain];
I don't think you are retaining the autoreleased dictionary created in your init method. You set the dictionary ivar but don't retain it. When you access it later, it is likely not valid anymore
Im having some trouble passing an NSNumber object to different threads.
I call a function on viewDidload that loads up some objects from core data as a background process. which calls another function which loops through the loaded objects to see if there are any images associated with it alredy downloaded. if its not present, download the images asynchronously and save it locally. The thing is I need to perform startDownloadFor:atIndex: on the main thread. But the application crashes because of the NSNumber object thats being passed. here is the code..
- (void)viewDidLoad {
...
...
[self performSelectorInBackground:#selector(loadImages) withObject:nil];
}
-(void)loadImages{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
...
...
[self fillInImages];
[pool release];
}
-(void)fillInImages{
NSString *imageURL;
for (int i=0; i < [dataManager.objectList count]; i++) {
...
if ([dataManager.RelatedImages Image] == nil) {
//[self startDownloadFor:imageURL atIndex:[NSNumber numberWithInt:i]; // << WORKS FINE
[self performSelectorOnMainThread:#selector(startDownloadFor:atIndex:) withObject:(imageURL, [NSNumber numberWithInt:i]) waitUntilDone:YES]; // << CRASHES
...
}else {
...
}
...
}
...
}
-(void)startDownloadFor:(NSString*)imageUrl atIndex:(int)indexPath{
NSString *indexKey = [NSString stringWithFormat:#"key%d",indexPath];
...
}
what is the right way of doing this?
Thanks
I've never seen that syntax passing more than one object to a selector - is that valid objective-c code? also, in your startDownloadFor:atIndex: you're passing in an NSNumber but the type for the second parameter on that selector is (int) - that can't be good ;)
The docs for performSelectorOnMainThread: say that the selector should take only one argument of type id. You're passing an invalid selector so I think that it's getting very confused about where the NSNumber is.
To fix it, pass an NSDictionary conatining the number and the image URL i.e.
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:imageURL, #"imageURL", [NSNumber numberWithInt:i], #"number", nil];
[self performSelectorOnMainThread:#selector(startDownload:) withObject:dict waitUntilDone:YES];
and
//-(void)startDownloadFor:(NSString*)imageUrl atIndex:(int)indexPath{
- (void)startdownload:(NSDictionary *)dict {
NSURL *imageURL = [dict objectForKey:#"imageURL"];
int indexPath = [[dict objectforKey:#"number"] intValue];
You are trying to pass 2 arguments into performSelectorOnMainThread:withObject:waitUntilDone: while the method only supports passing one argument.
You need to use NSInvocation to send more arguments (or use an NSDictionary like dean proposed).
SEL theSelector;
NSMethodSignature *aSignature;
NSInvocation *anInvocation;
theSelector = #selector(startDownloadFor:atIndex:);
aSignature = [self instanceMethodSignatureForSelector:theSelector];
anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:self];
// indexes for arguments start at 2, 0 = self, 1 = _cmd
[anInvocation setArgument:&imageUrl atIndex:2];
[anInvocation setArgument:&i atIndex:3];
[anInvocation performSelectorOnMainThread:#selector(invoke) withObject:NULL waitUntilDone:YES];
I have got I have got two methods both in different classes. One is class method and other is instance method. i am calling class method from instance method. When instance method finishes it gives runtime error "EXC_BAD_ACCESS".
#import "xmlObject.h"
#import "textmeAppDelegate.h"
#implementation Class1
- (void)method1 {
textmeAppDelegate *del = (textmeAppDelegate *)[[UIApplication sharedApplication] delegate];
NSArray *bgColor = [[NSArray alloc] initWithArray:[xmlObject fetchImmediateChildrenValues:[del.navigationbarStyle objectForKey:#"backgroundcolor"]]];
UIColor *color = [UIColor colorWithRed:[[bgColor objectAtIndex:3] floatValue] green:[[bgColor objectAtIndex:2] floatValue] blue:[[bgColor objectAtIndex:1] floatValue] alpha:[[bgColor objectAtIndex:0] floatValue]];
CGContextSetFillColor(context, CGColorGetComponents([color CGColor]));
CGContextFillRect(context, rect);
[bgColor release];
}
#end
#implementation xmlObject
+ (NSArray *) fetchImmediateChildrenValues:(NSMutableDictionary *) node {
NSMutableDictionary *tmp = [[node objectForKey:#"children"] retain];
NSArray *keys = [[NSArray alloc] initWithArray:[tmp allKeys]];
keys = [keys sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
NSMutableArray *pushArr = [[[NSMutableArray alloc] init] autorelease];
NSString *val = [[NSString alloc] init];
for(NSString *str in keys) {
val = (NSString *)[[tmp objectForKey:str] objectForKey:#"innertext"];
[pushArr addObject:val];
}
[val release];
[keys release];
return [NSArray arrayWithArray:pushArr];
}
#end
What is wrong with the code? Also app is crashing for this line of code
application is crashing if i include this line
NSArray *bgColor = [[NSArray alloc] initWithArray:[xmlObject fetchImmediateChildrenValues:[del.navigationbarStyle objectForKey:#"backgroundcolor"]]];
If I remove it application runs smoothly.
I have several comments on your code. One of them is the immediate cause of your crash, but you need to fix at least one other issue too. The short answer is that you over release val and keys.
NSArray *bgColor = [[NSArray alloc] initWithArray:[xmlObject fetchImmediateChildrenValues:[del.navigationbarStyle objectForKey:#"backgroundcolor"]]];
You don't need to create a new array here, you can simply write the following:
NSArray *bgColor = [xmlObject fetchImmediateChildrenValues:[del.navigationbarStyle objectForKey:#"backgroundcolor"]];
if you do, you don't need the [bgColor release] further down.
NSArray *keys = [[NSArray alloc] initWithArray:[tmp allKeys]];
keys = [keys sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
These two lines leak the first NSArray, you alloc it but you overwrite it straight away with the sorted version. In fact, you can simply write:
keys = [[tmp allKeys] sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
Note that you do not own keys so you can get rid of the [keys release] line further down.
NSString *val = [[NSString alloc] init];
for(NSString *str in keys) {
val = (NSString *)[[tmp objectForKey:str] objectForKey:#"innertext"];
[pushArr addObject:val];
}
[val release];
This is the source of your immediate problem. You first alloc a new string. Then you immediately overwrite it on each iteration of your loop. So the allocated NSString leaks. You do not own the val returned by [[tmp objectForKey:str] objectForKey:#"innertext"]; on each iteration, so the release ov val after the loop should not be there.
On a side note, objectForKey: returns an id - the cast to NSString* is redundant. Most people leave it out.
[keys release];
Going back to the bit above where I told you that you were leaking your alloc'd keys? Well the new version of keys you overwrote it with you don't own. Therefore you must not release keys here.
return [NSArray arrayWithArray:pushArr];
This is fine. My preference would be for:
return [[pushArray copy] autorelease];
but it is just a matter of style. You could also just return pushArray, but pushArray is mutable and the caller may rely on the return value being immutable.
Test your code with NSZombieEnabled set... It should give you enough informations to fix your problem.