Iphone: Passing objects and Multiple threads - iphone

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];

Related

How to pass 3 parameters to #selector

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]]];
}

Populating NSDictionary and NSArrays for Model data

I'm trying to create an NSDictionary full of arrays in the implementation file of my model but my code hasn't worked yet. I want to create arrays that are lists of types of dogs and cats and then add those arrays to a dictionary with keys called DOG and CAT. Here is my code:
#implementation wordDictionary
#synthesize catList = _catList;
#synthesize dogList = _dogList;
#synthesize standardDictionary =_standardDictionary;
- (void)setCatList:(NSMutableArray *)catList
{
self.catList = [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (void)setDogList:(NSMutableArray *)dogList
{
self.dogList = [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(void)setStandardDictionary:(NSMutableDictionary *)standardDictionary
{
[self.standardDictionary setObject: _catList forKey:#"CAT"];
[self.standardDictionary setObject: _dogList forKey:#"DOG"];
}
- (NSString*)selectKey
{
NSInteger keyCount = [[self.standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[self.standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
#end
This code is the model. The model is hooked up to my view controller such that when a user taps a button, the NSString returned from randomKey is displayed in a label on the screen. So the text will read either CAT or DOG. Here's the code for that:
- (IBAction)changeGreeting:(UIButton*)sender {
NSString *chosenKey = [self.dictionary selectKey];
NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = labelText;
}
Unfortunately when I tap the button on the simulator I get an error message saying: Thread 1:EXC_ARITHMETIC (code=EXC_1386_DIV, subcode=0x0) at NSInteger randomKeyIndex = arc4random() % keyCount; and it appears that I'm getting it because neither my NSArray nor my NSDictionary have any objects inside of them.
Does anyone have any idea why my NSArray and NSDictionary haven't been populated?
Thanks very much.
The simple answer is that there isn't any code here that calls the methods to set the arrays or dictionary.
But the real underlying issue is that there are a couple of bad 'patterns' going on here that you should fix:
In your setter methods (setCatList:, setDogList:, setStandardDictionary:) you're not setting the properties in question to the values that are passed in. For example, you should be setting catList to the passed in "catList" variable.
- (void)setCatList:(NSMutableArray *)catList
{
if (_catList != catList) {
[_catList release];
_catList = [catList retain];
}
}
Then you should have some kind of "setup" happening, usually in a method in the view controller like viewDidLoad:
[wordDictionary setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
// and more for the other two setters
Alternately, you can set these default values in the init for the wordDictionary class:
- (id)init {
self = [super init];
if (self) {
[self setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
}
return self;
}
The former is better in most cases, but you may have a good reason to pre-populate your model for all instances of the class.
Assuming you called setCatList:, setDogList: and setStandardDictionary: before. Probably that causing is this :
NSString *chosenKey = [self.dictionary selectKey];
change into this :
NSString *chosenKey = [self selectKey];
UPDATE
I'm trying to make your life easier. no need to create your object if you don't need the most.
- (NSMutableArray*)getCatList
{
return [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (NSMutableArray*)getDogList
{
return [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(NSMutableDictionary*)getStandardDictionary
{
NSMutableDictionary *standardDictionary = [NSMutableDictionary new];
[standardDictionary setObject:[self getCatList] forKey:#"CAT"];
[standardDictionary setObject:[self getDogList] forKey:#"DOG"];
return [standardDictionary autorelease];
}
- (NSString*)selectKey
{
NSMutableDictionary *standardDictionary = [self getStandardDictionary];
NSInteger keyCount = [[standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
- (IBAction)changeGreeting:(UIButton*)sender {
// NSString *chosenKey = [self selectKey];
//NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = [self selectKey]; //no need to convert it to NSString again
}
Two things to consider:
I don't see you calling these:
setCatList:(NSMutableArray*)catList;
setDogList:(NSMutableArray*)dogList;
You use self.catList and self.dogList, but neither of those are synthesized, instead you have beatList and meList synthesized
Change the synthesizes to the catList and dogList, and make sure you call the set list methods, and then you should make some progress.

NSMutable Array: Getting "out of scope" Status After Mutable Copying

I have a SOAP service and I generated classes and functions on SudzC.com.
So I'm using the soap functions they generated, it returns an NSMutableArray with objects that are inherited by my custom class(which is generated by them, too).
So far everything's good. My values are getting into the array and I could see any property of any object with one condition: Only inside of the function that's handling the service.
Just to make it clear, here is the code:
- (void)viewDidLoad
{
SDZGeneratedWebService* service = [SDZGeneratedWebService service];
service.logging = YES;
[service callMyData:self action:#selector(callMyDataHandler:) dataId: 1];
[super viewDidLoad];
}
- (void) callMyDataHandler: (id) value {
// Handle errors
if([value isKindOfClass:[NSError class]]) {
NSLog(#"%#", value);
return;
}
// Handle faults
if([value isKindOfClass:[SoapFault class]]) {
NSLog(#"%#", value);
return;
}
// Do something with the NSMutableArray* result
NSMutableArray *result = (NSMutableArray *)value;
MyCustomClass *myObject = [result objectAtIndex:0];
NSLog(#"%#", myObject.myProperty); //Works Great
}
Like I said, so far everything's perfect. But I need to use the data outside of this function.
So in my .h file, I created an array like NSMutableArray *myDataArray;
When I intend to copy the result array to myDataArray, it copies the objects(I can see that the myDataArray.count value is equal to result array's) but all the objects are "out of scope". So I cannot use them.
I also tried to copy all objects by indexes in a for loop, nope, the objects are getting their values, but when I "addObject" to myDataArray, same, out of scope.
What is wrong here? Can't I generate an array of a custom class this way?
Edit: The code I'm generating myDataArray:
myDataArray = [[NSMutableArray alloc] init];
[myDataArray removeAllObjects];
for (int i=0; i<((NSMutableArray *)result).count; i++) {
MyCustomClass *myObject = [result objectAtIndex:i];
[myDataArray addObject:myObject];
[myObject release];
}
[self.tableView reloadData];
} //(End of callMyDataHandler function)
I before tried this way, too:
[myDataArray removeAllObjects];
duyurular = [result mutableCopy];
} //(End of callMyDataHandler function)
You can copy objects from one array to another using this method:
NSArray *source;
NSArray *dst = [[NSArray alloc] initWithArray:source];
In your code you should remove line: [myObject release]; and I would better call [((NSMutableArray *)result) count] rather then using dot notation.

How to handle returned object from function to avoid memory leaks?

Suppose I have a function
- (NSString *)fullNameCopy {
return [[NSString alloc] initWithFormat:#"%# %#", self.firstName, self.LastName];
}
Can somebody tell me how to call this function, how to assign its value to a new object, and how then to release it, avoiding memory leaks, and bad access.
Would it be like
NSSting *abc = [object fullNameCopy];
// Use it and release
[abc release];
or I should alloc abc string too ?
Update:
The point here, Can I return non-autorelease objects from a function and then release them in the calling function. As per Obj-C function naming conventions, a function name containing alloc or copy should return object assuming that calling function has the ownership.
As in above case, my function "fullNameCopy" return a non-autoreleased abject, and I want to release them in the calling function.
You are right. Since the method name contains the word ‘copy’, Cocoa convention dictates that the method returns an object that is owned by the caller. Since the caller owns that object, it is responsible for releasing it. For example:
- (void)someMethod {
NSString *abc = [object fullNameCopy];
// do something with abc
[abc release];
}
Alternatively, you could use -autorelease instead of -release:
- (void)someMethod {
NSString *abc = [[object fullNameCopy] autorelease];
// do something with abc
}
Refer this post
UPDATE:
- (NSString *)fullNameCopy {
NSString *returnString = [NSString stringWithFormat:#"%# %#", self.firstName, self.LastName]; // Autorelease object.
return returnString;
}
-(void) someFunction {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *fullName = [self fullNameCopy];
[pool release]
}
Like This:
- (NSString *)fullName {
NSString * retVal = [[NSString alloc] initWithFormat:#"%# %#", self.firstName, self.LastName];
return [retVal autoRelease];
}
Then
NSSting *abc = [object fullName];
return [[[NSString alloc] initWithFormat:#"%# %#", self.firstName, self.LastName]autorelease];

Run-Time Dynamic Length Actions list for Sequence

Cocos2D defines the static method 'actions' for the Sequence class as such:
+(id) actions: (FiniteTimeAction *) action1, ... { /* omitted */ }
How could I build a list of actions to perform at run-time, perhaps read from a disk file or such?
I read that the variable length argument list can be translated into a (char *) and passed in that way ...
NSMutableArray *actions = [[NSMutableArray alloc] init];
[actions addObject: [DelayTime actionWithDuration:1]];
[actions addObject: [ScaleBy actionWithDuration:2 scale:4];
char *argList = (char *)malloc(sizeof(FiniteTimeAction *) * [actions count]);
[actions getObjects:(id *)argList];
[self runActions: actions];
Is this the 'best way' or the 'correct' way to do this? Are their better alternatives, faster alternatives?
The vaargs is just a helper to build nested Sequence objects. It returns a FiniteTimeAction* which is built by successive calls to [Sequence actionOne:one_ two:two_]. You could do this yourself in your code by looping through your set or array. It should go something like this:
FiniteTimeAction *seq = nil;
for (FiniteTimeAction *action in actions) {
if (!seq) {
seq = action;
} else {
seq = [Sequence actionOne:seq two:action];
}
}
[self runActions:seq];
I would look at using NSInvocation - you can basically build one using the method signature you are targeting, and then use the setters for each object like so:
NSInvocation *invoker = setup invoker here...
for ( int i = 0; i < actions.count; i++ )
{
NSObject *arg = [actions objectatIndex:i];
[invoker setArgument:&arg atIndex:i+2];
}
[invoker setArgument:nil atIndex:i+2];
The i+2 bit is because the first two arguments are really self and _cmd, so you set everything from index 2 and on... read the docs on setArgument:atIndex: in NSInvocation for more detail.
Once you have that you can invoke the operation with a target. I've never used this with variable argument methods so I'm unsure how well it works there, but it's the only means I know of to dynamically construct a call at runtime.
Given the options provided, it appears that the only real way to accomplish what I was after is to use the approach I mentioned in the question, which is was:
NSMutableArray *actions = [[NSMutableArray alloc] init];
[actions addObject: [DelayTime actionWithDuration:1]];
[actions addObject: [ScaleBy actionWithDuration:2 scale:4];
char *argList = (char *)malloc(sizeof(FiniteTimeAction *) * [actions count]);
[actions getObjects:(id *)argList];
[self runActions: actions];
Perhaps a set of pre-built sequences?
id move = [MoveBy actionWithDuration:3 position:ccp(350,0)];
id move_back = [move reverse];
id move_ease_in = [EaseIn actionWithAction:[[move copy] autorelease] rate:3.0f];
id move_ease_in_back = [move_ease_in reverse];
id move_ease_out = [EaseOut actionWithAction:[[move copy] autorelease] rate:3.0f];
id move_ease_out_back = [move_ease_out reverse];
id seq1 = [Sequence actions: move, move_back, nil];
id seq2 = [Sequence actions: move_ease_in, move_ease_in_back, nil];
id seq3 = [Sequence actions: move_ease_out, move_ease_out_back, nil];
[grossini runAction: [RepeatForever actionWithAction:seq1]];
[tamara runAction: [RepeatForever actionWithAction:seq2]];
[kathia runAction: [RepeatForever actionWithAction:seq3]];