I'm a beginner at C, Obj-C and the iPhone, and I'm trying getting to grips with a lot of terminology used. I hope one of ye can help with a problem I have been struggling with for a few days now.
My code below is a method which call up a nib containing a search field and a table. The table is populated from a search of the array created for 'theList' below. Using 'Instruments', I am getting a Leak at the line:
NSDictionary *theItem = [NSDictionary dictionaryWithObjectsAndKeys:clientName,#"Name",clientId,#"Id",nil]; , but I can't figure out why :(
I know it's probably a difficult question to answer, but if any one can be of any help!
- (void)editClient:(id)sender {
if (pickList == nil) {
pickList = [[PickFromListViewController alloc] initWithNibName:#"PickList" bundle:nil];
}
TimeLogAppDelegate *appDelegate = (TimeLogAppDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableArray *theList = [[NSMutableArray alloc] init];
int i;
for (i=0;i < [appDelegate.clients count];i++) {
Client *thisClient = [appDelegate.clients objectAtIndex:i];
NSString *clientName = [[NSString alloc] initWithString: thisClient.clientsName];
NSNumber *clientId = [[NSNumber alloc] init];
clientId = [NSNumber numberWithInt:thisClient.clientsId];
NSDictionary *theItem = [NSDictionary dictionaryWithObjectsAndKeys:clientName,#"Name",clientId,#"Id",nil];
[theList addObject:theItem];
theItem = nil;
[clientName release];
[clientId release];
}
[pickList createSearchItems:theList :NSLocalizedString(#"Client",nil)];
[theList release];
appDelegate.returningID = [NSNumber numberWithInt: projectsClientsId];
[self.navigationController pushViewController:pickList animated:YES];
}
Thanks in advance!
This returns allocated NSNumber instance.
NSNumber *clientId = [[NSNumber alloc] init];
This line overwrites the above clientId with another instance of NSNumber, numberWithInt returns autoreleased object, since you haven't allocated memory for it you should not call release, it will be released automatically.
clientId = [NSNumber numberWithInt:thisClient.clientsId];
You are calling release on clientId so you get memory problem.
To fix it remove the first line above which is useless in this case and update the second one to:
NSNumber * clientId = [NSNumber numberWithInt:thisClient.clientsId];
Then remove the:
[clientId release]
Because the clientId will be released automatically.
EDIT: Re still have problems ...
I'm not sure how to you manipulate the clients in app delegate, otherwise the code should work ok, I created small example, omitting the parts that I can't see (app delegate and clients):
// command line utility - foundation tool project:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableArray * theList = [[NSMutableArray alloc] init];
int i = 0;
for (i = 0; i < 10; ++i)
{
NSString * clientName = [NSString stringWithString:#"client"]; //no need to release
NSNumber * clientId = [NSNumber numberWithInt:i];
NSDictionary * theItem = [NSDictionary dictionaryWithObjectsAndKeys:
clientName, #"name",
clientId, #"id",
nil];
[theList addObject:theItem];
}
for (id item in theList) for (id key in item) NSLog(#"%# - %#", key, [item objectForKey:key]);
[theList release];
[pool drain];
return 0;
}
You are creating clientID with [[NSNumber alloc] init], and then immediately overwriting it with an autoreleased NSNumber instance [NSNumber numberWithInt], and then you are releasing it later in your code, which you shouldn't do. Get rid of the [[NSNumber alloc] init] line and the [clientId release] line and that should fix it up a little.
Aside from the obvious leak of the NSNumber, there are a few other things I'd fix that may help. Most are fairly minor, but in my experience with Objective-C, less code == clearer code, something that is not equally true for languages like Bash or Perl. ;-)
- (void) editClient:(id)sender {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (pickList == nil) {
pickList = [[PickFromListViewController alloc] initWithNibName:#"PickList" bundle:nil];
}
TimeLogAppDelegate *appDelegate = (TimeLogAppDelegate*)[[UIApplication sharedApplication] delegate];
NSMutableArray *searchItems = [NSMutableArray array];
NSMutableDictionary *itemDict = [NSMutableDictionary dictionary];
for (Client *client in appDelegate.clients) {
[itemDict setObject:[client.clientsName copy] forKey:#"Name"];
[itemDict setObject:[NSNumber numberWithInt:client.clientsId] forKey:#"Id"];
[searchItems addObject:[[itemDict copy] autorelease]];
}
[pickList createSearchItems:searchItems :NSLocalizedString(#"Client",nil)];
[self.navigationController pushViewController:pickList animated:YES];
appDelegate.returningID = [NSNumber numberWithInt: projectsClientsId];
[pool drain];
}
There are a few mysterious points that make me suspicious:
The line just after the for loop tells pickList to do something with the NSMutableArray. That method should retain the new array, as well as release the old array if one exists. If you just overwrite the pointer, the old array will be leaked. (Also, this method is poorly named. Anonymous arguments (a colon with no preceding text) are legal in Objective-C, but considered extremely bad practice. Consider renaming the method to better express what it does.)
The next line seems to associate the pick list with a navigation controller. If it is custom code, make sure the -pushViewController:animated: method properly releases an existing pick list when a new one is specified.
Assigning to appDelegate.returningID is assumed to call the setter for a returningID property. Be sure that property retains or copies the NSNumber as necessary.
Memory leaks can be tricky to track down, even in Instruments, and you'll often find that it looks like Foundation classes (such as NSDictionary) are leaking like a sieve, but I have always been able to trace it back to an abnormality in my code. :-)
Related
I am using XCode for developing an iPhone app. I am new to this platform and need some help with a particular issue...
I have a method that processes some data and returns two integer values as NSNumber wrapped into a NSMutableArray.
Here is the method:
-(NSMutableArray *)processPoints:(int) x:(int) y
{
NSMutableArray *mutArray = [[NSMutableArray alloc] initWithCapacity:3];
int x9,y9;
// ...do some processing...
NSNumber* xNum = [NSNumber numberWithInt:x9];
NSNumber* yNum = [NSNumber numberWithInt:y9];
[mutArray addObject:xNum];
[mutArray addObject:yNum];
return [mutArray autorelease];
}
I call the above method from another method, where I copy the NSNumber stuff into local variables and then release the local copy of NSMutable array.
But the app crashes when releasing this NSMutable array (variable 'mutArray').
Here is the method:
-(void)doNinjaAction
{
NSMutableArray* mutArray = [self processPoints: x :y];
NSNumber* s1 = [[mutArray objectAtIndex:0] retain];
NSNumber* s2 = [[mutArray objectAtIndex:1] retain];
x = [s1 integerValue];
y = [s2 integerValue];
//...proceed with other stuff...
[mutArray autorelease]; //this is where the system crashes. same for 'release'
//instead of 'autorelease'
}
Can you please explain where I am going wrong with the process of memory release.
My understanding of the process is a bit shaky. Please help.
Because you're overreleasing the array. You alloc-init it in processPoints:, then you autorelease it - that's correct, this is how you dispose of its ownership.
After that, you don't need to and must not autorelease or release it once again. This is not malloc() from the standard library.
when you call the statement
NSMutableArray* mutArray = [self processPoints: x :y];
This itself acts as autorelease.
Hence releasing the array explicitly will cause the app to crash.
You are releasing mutArray more then once. Once in processPoints function and again in doNinjaAction.
To resolve the crash remove :
[mutArray autorelease];
-(NSMutableArray *)processPoints:(int) x:(int) y
{
NSMutableArray *mutArray = [[NSMutableArray alloc] initWithCapacity:3];
int x9,y9;
// ...do some processing...
NSNumber* xNum = [NSNumber numberWithInt:x9];
NSNumber* yNum = [NSNumber numberWithInt:y9];
[mutArray addObject:xNum];
[mutArray addObject:yNum];
[mutArray autorelase];
return mutArray;
}
try this one it'll resolve it.
-(NSMutableArray *)processPoints:(int) x:(int) y
{
NSMutableArray *mutArray =[[[NSMutableArray alloc] initWithCapacity:3]autorelease];
int x9,y9;
// ...do some processing...
NSNumber* xNum = [NSNumber numberWithInt:x9];
NSNumber* yNum = [NSNumber numberWithInt:y9];
[mutArray addObject:xNum];
[mutArray addObject:yNum];
return mutArray;
}
As #H2CO3 and #AppleDelegate suggested, it is right.
Still I would suggest to use ARC and convert your project to ARC enabled.
Go to Edit->Refactor->Convert to Objectiv-C ARC
Then you dont need to do any releases anywhere. It will take care itself of all the releases :)
I'm using this code with ARC:
NSMutableDictionary *datesDict = [[NSMutableDictionary alloc]init];
NSMutableArray *datesArray = [[NSMutableArray alloc]init];
for (NSString *key in programsArray) {
datesArray = [_onDemandDictionary objectForKey:key];
NSMutableArray *newDates = [[NSMutableArray alloc]init];
int count;
for (count = 0; count <datesArray.count; count++) {
NSMutableDictionary *programsDict = [[NSMutableDictionary alloc]init];
programsDict = [datesArray objectAtIndex:count];
[newDates addObject:[programsDict objectForKey:#"date"]];
}
[datesDict setObject:newDates forKey:key];
}
But when I run the analyzer tool I'm getting value stored to (datesArray and programsDict) during its initialization is never read on lines:
NSMutableArray *datesArray = [[NSMutableArray alloc]init];
programsDict = [datesArray objectAtIndex:count];
Why is this happening how do I get hid of the warning?
Thank you!
The issue is you create a new NSMutableArray and assign it to datesArray at the beginning
NSMutableArray *datesArray = [[NSMutableArray alloc]init];
Then almost immediately after you assign a completely different value to datesArray with
datesArray = [_onDemandDictionary objectForKey:key];
I would just start with
NSMutableArray *datesArray = nil;
It's the same concept for programsDict.
On line 2, you create a new array datesArray.
Then, on line 6 (first line of the for loop), you set a new value to datesArray.
The compiler is just warning you that the line 2 has no effect, and that the code is bugged (in the sense it does not do what you expect).
True, the programsArray could be an empty array, and in this case you want datesArray to just be initialized to use it after the snippet you showed us, but it would be better to make this explicit.
For programsDict, it is even easier: you initialize it with ... alloc] init] then set it to an object of datesArray, making the first operation useless.
You are not using datesArray in your loop, you are simply assigning it values, So either take it nil array like
NSMutableArray* datesArray = nil;
or like
NSMutableArray *datesArray;
to remove waring .
I eliminated all the leaks from my current app. However Instruments constantly tells me that I have a leak in the method shown below.
The leak is of type NSMutableArray and has a size of either 16 or 32 bytes. Yes, I know that's not much but it adds up. Also see it as an academic question that I need to solve to make my code leakless.
+ (id) meterFromDict:(NSDictionary*)dict {
Meter* resMeter = [[Meter alloc] initWithType:[[dict objectForKey:#"MeterBase"] intValue]];
//NSLog(#"dict: %#",dict);
resMeter.volume = nil;
resMeter.sounds = nil;
resMeter.repeats = nil;
resMeter.volume = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"volumeArray"]] autorelease];
resMeter.sounds = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"soundsArray"]] autorelease];
resMeter.repeats = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"repeatsArray"]] autorelease];
//NSLog(#"MeterFromDict called and resmeter.repeats count is : %i",[resMeter.repeats count]);
resMeter.bpm = [[dict objectForKey:#"BPM"] floatValue];
return [resMeter autorelease];
}
Without looking at your Instruments output directly I can't tell exactly, but you're writing some redundant code: Try this:
+ (id) meterFromDict:(NSDictionary*)dict {
Meter* resMeter = [[Meter alloc] initWithType:[[dict objectForKey:#"MeterBase"] intValue]];
//NSLog(#"dict: %#",dict);
resMeter.volume = [dict objectForKey:#"volumeArray"];
resMeter.sounds = [dict objectForKey:#"soundsArray"];
resMeter.repeats = [dict objectForKey:#"repeatsArray"];
//NSLog(#"MeterFromDict called and resmeter.repeats count is : %i",[resMeter.repeats count]);
resMeter.bpm = [[dict objectForKey:#"BPM"] floatValue];
return [resMeter autorelease];
}
There's no point in nilling your properties before assigning new values to them.
Also, No point creating new arrays for arrays that you already have. And if you have properly declared your volume, sounds and repeats properties with copy instead of retain.
Try that and see if it works better.
I created a custom NSString Category which lets me find all strings between two other strings. I'm now running into the problem of finding that there are a lot of kBs leaking from my script. Please see code below:
#import "MyStringBetween.h"
#implementation NSString (MyStringBetween)
-(NSArray *)mystringBetween:(NSString *)aString and:(NSString *)bString;
{
NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
NSArray *firstlist = [self componentsSeparatedByString:bString];
NSMutableArray *finalArray = [[NSMutableArray alloc] init];
for (int y = 0; y < firstlist.count - 1 ; y++) {
NSString *firstObject = [firstlist objectAtIndex:y];
NSMutableArray *secondlist = [firstObject componentsSeparatedByString:aString];
if(secondlist.count > 1){
[finalArray addObject:[secondlist objectAtIndex:secondlist.count - 1]];
}
}
[autoreleasepool release];
return finalArray;
}
#end
I admit that I'm not super good at releasing objects, but I had believed that the NSAutoreleasePool handled things for me.
The line that is leaking:
NSMutableArray *secondlist = [firstObject componentsSeparatedByString:aString];
Manually releasing secondlist raises an exception.
Thanks in advance!
No, this is the line that is leaking:
NSMutableArray *secondlist = [[NSMutableArray alloc] init];
And it isn't that big of a leak (just an empty mutable array). Still, don't do that.
In particular, the line:
secondlist = [[firstlist objectAtIndex:y] componentsSeparatedByString:aString];
Is assigning over the reference to the empty mutable array.
Also FinalArray should be named finalArray.
finalArray is leaking. You should autorelease it before returning it but make sure you do it either before allocating the autorelease pool or after releasing it.
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.