Memory leak while handling Foundation Object from NSJSONSerialization - objective-c

I'm struggling to fix a memory leak in a helper function I have made. The helper function takes the result of
+ (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError * _Nullable *)error
and converts all the leaf elements into NSStrings if they are NSNumbers.
Here is the method:
-(NSArray *) stringisizeObjects:(NSArray *)inputArray{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *mutable = [[NSMutableArray alloc] initWithCapacity:[inputArray count]];
for (int i = 0; i < [inputArray count]; i++) {
NSArray *keys = [inputArray[i] allKeys];
NSMutableDictionary *addDictionary = [[NSMutableDictionary alloc] initWithCapacity:[keys count]];
for (int j = 0; j < [keys count]; j++) {
id theObject = [[inputArray[i] objectForKey:keys[j]]autorelease];
if ([theObject isKindOfClass:[NSNumber class]]) {
[addDictionary setObject:[theObject stringValue] forKey:keys[j]];
[theObject release];
}else if ([theObject isKindOfClass:[NSString class]]){
[addDictionary setObject:[inputArray[i] objectForKey:keys[j]] forKey:keys[j]];
}
}
[mutable addObject:addDictionary];
}
NSArray *returnArray = [mutable copy];
[mutable removeAllObjects];
[mutable release];
[pool drain];
return returnArray;
}
Here is how I get the input array.
id parsedThingy = [NSJSONSerialization JSONObjectWithData:resultJSONData options:1 error:&jsonDecodeError];
Before I can pass the result to my stringisize method I must ensure that I have an NSArray of NSDictionaries with matching keys.
NSArray *resultArray = [self stringisizeObjects:parsedThingy];
The X-Code memory leaks tool has pointed me to this method as the cause of my problem.
Instruments showing leaks
As you can see I have tried wrapping things in autorelease pools, autoreleasing and releasing. I just don't see any way forward here.
This is a non ARC project that runs 24/7.
Edit: I took the advice from Droppy and tried to re-write the method using mutableCopy. The leak is still there. At this point my only work around maybe to change the source of the JSON to send only strings. :(
-(NSArray *) stringisizeObjects2:(NSArray *)inputArray{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *mutableArray = [inputArray mutableCopy];
for (int i = 0; i < [mutableArray count]; i++) {
NSMutableDictionary *mutableDict = [mutableArray[i] mutableCopy];
NSArray *keys = [mutableDict allKeys];
for (int j = 0; j < [keys count]; j++) {
if ([[mutableDict objectForKey:keys[j]] isKindOfClass:[NSNumber class]]) {
NSString *stringValue = [[mutableDict objectForKey:keys[j]] stringValue];
[mutableDict removeObjectForKey:keys[j]];
[mutableDict setObject:stringValue forKey:keys[j]];
}
}
mutableArray[i] = [mutableDict copy];
[mutableDict release];
}
NSArray *returnArray = [mutableArray copy];
[mutableArray release];
[pool drain];
return returnArray;
}

problem:
addDictionary called alloc but not call release or autorelease
returnArray = [mutable copy]; // did increase retainCount +1, need autorelease here
id theObject = [inputArray[i] objectForKey:keys[j]]; // not need autorelease or release for object that You not own
add NSAutoreleasePool to top an bottom here just do nothing
solution:
-(NSArray *) stringisizeObjects:(NSArray *)inputArray{
NSMutableArray *mutable = [[NSMutableArray alloc] initWithCapacity:[inputArray count]];
for (int i = 0; i < [inputArray count]; i++) {
NSArray *keys = [inputArray[i] allKeys];
NSMutableDictionary *addDictionary = [[NSMutableDictionary alloc] initWithCapacity:[keys count]];
for (int j = 0; j < [keys count]; j++) {
id theObject = [inputArray[i] objectForKey:keys[j]]; // not need autorelease
if ([theObject isKindOfClass:[NSNumber class]]) {
[addDictionary setObject:[theObject stringValue] forKey:keys[j]];
//[theObject release]; // not need release value here
}else if ([theObject isKindOfClass:[NSString class]]){
[addDictionary setObject:[inputArray[i] objectForKey:keys[j]] forKey:keys[j]];
}
}
[mutable addObject:addDictionary];
[addDictionary release]; // release after not use
}
NSArray *returnArray = [[[NSArray alloc] initWithArray:mutable] autorelease]; // auto release for return value
[mutable removeAllObjects];
[mutable release];
return returnArray;
}

Related

Why Instruments is showing so many leaks in the following code?

- (NSArray *) makeKeyValueArray: (NSArray *) arr
{
NSMutableArray *result = [[NSMutableArray alloc] init];
for(int i = 0; i < [arr count]; i++)
{
[result addObject:[[KeyValue alloc] initWithData:[arr objectAtIndex:i] :[arr objectAtIndex:i]]];
}
return result;
}
Instruments is showing 188 leaks in the above code, why is that? can anyone please explain it to me?
- (NSArray *) makeKeyValueArray: (NSArray *) arr
{
NSMutableArray *result = [[NSMutableArray alloc] init];
for(int i = 0; i < [arr count]; i++)
{
id obj = [[KeyValue alloc] initWithData:[arr objectAtIndex:i] :[arr objectAtIndex:i]]; // obj reference count is now 1, you are the owner
[result addObject:obj]; //reference count is now 2, the array is also an owner as well as you.
[obj release];// reference count is now 1, you are not the owner anymore
}
return [result autorelease];
}
Take a look at Basic Memory Management Rules
you must relinquish ownership of an object you own

Extract a NSDictionary from a NSMutableArray

I need to extract a NSDictionary from a NSMutableArray, and extract an object from that dictionary.
The code should be quite easy, but I keep having a SIGABRT error on the NSDictionary declaration.
-(void)calcolaConto {
conto = [[NSNumber alloc] initWithDouble:0];
for (int i=0; [shoppingListItems count]; ++i) {
NSDictionary *dictVar = (NSDictionary *) [shoppingListItems objectAtIndex:i]; //<-- SIGABRT
NSNumber *IO = (NSNumber *) [dictVar objectForKey:#"incout"];
NSNumber *priceValue = (NSNumber *) [dictVar objectForKey:#"price"];
if ([IO isEqualToNumber:[NSNumber numberWithInt:0]]) {
conto = [NSNumber numberWithDouble:([conto doubleValue] + [priceValue doubleValue])];
} else if ([IO isEqualToNumber:[NSNumber numberWithInt:1]]) {
conto = [NSNumber numberWithDouble:([conto doubleValue] - [priceValue doubleValue])];
}
NSLog(#"Valore %#", conto);
}
}
"shoppingListItems" is created in this way:
NSMutableDictionary *rowDict = [[NSMutableDictionary alloc] initWithCapacity:6];
[rowDict setObject:primaryKeyValue forKey: ID];
[rowDict setObject:itemValue forKey: ITEM];
[rowDict setObject:priceValue forKey: PRICE];
[rowDict setObject:groupValue forKey: GROUP_ID];
[rowDict setObject:incOut forKey:INC_OUT];
[rowDict setObject:dateValue forKey: DATE_ADDED];
[shoppingListItems addObject: rowDict];
The problem is that your loop never stops. You should use:
for (NSUInteger i = 0; i < [shoppingListItems count]; i++) {
or:
for (NSDictionary* dictVar in shoppingListItems) {
so that you do not try to access an element that is out of bounds. In your
current loop i will be incremented until it reaches [shoppingListItems count]
which is beyond the end of the array, so objectAtIndex will throw an exception.

Problem realeasing allocated objects

Crash occurs at [searchDict release]. If I switch order of the two latest lines it still crash on the latest line (now [searchArray release]). I'm quite new to Objective C and I guess I haven't got the alloc/release right... Help? :)
NSMutableDictionary *searchDict = [[NSMutableDictionary alloc] init];
NSMutableArray *searchArray = [[NSMutableArray alloc] init];
for(int i = 0; i < 2; i++) { // Run two times ("Favorites" and "All")
searchDict = [listOfItems objectAtIndex:i];
searchArray = [searchDict objectForKey:#"Entries"];
for (NSString *sTemp in searchArray) {
NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
[listOfItemsDynamic addObject:sTemp];
}
}
[searchArray release];
[searchDict release];
You allocate space and assign it to the variables:
NSMutableDictionary *searchDict = [[NSMutableDictionary alloc] init];
NSMutableArray *searchArray = [[NSMutableArray alloc] init];
But then you assign non locally-allocated data to them:
searchDict = [listOfItems objectAtIndex:i];
searchArray = [searchDict objectForKey:#"Entries"];
So basically, you don't need to allocate and release. Instead do something like this:
NSMutableDictionary *searchDict; // Just declartion, no allocation / init
NSMutableArray *searchArray; // Just declartion, no allocation / init
for(int i = 0; i < 2; i++) { // Run two times ("Favorites" and "All")
searchDict = [listOfItems objectAtIndex:i];
searchArray = [searchDict objectForKey:#"Entries"];
for (NSString *sTemp in searchArray) {
NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
[listOfItemsDynamic addObject:sTemp];
}
}
// No release needed here
If you are familiar with C, it is similar to:
char *pChar;
pChar = malloc(15); // allocate memory and assign to pChar
pChar = "Hello"; // assign new address to pChar
free(pChar); // Error here ;)

potential leak problems

when I build and analize my application , am getting potential leak near the code [array1 release]...why its happening there..?thanks in advance
- (void) touchOnFeaturedCatalog
{
searchId == 2;
//featuredCatalogName = #"23064_Leeds2010";
//NSString *response = [ZoomCatalogAppDelegate getResponseFromServer:[NSString stringWithFormat:#"http://www.zoomcatalog.com/iphone/iphone.php?catalog=%#&iphone=Yes&pdf=No", featuredCatalogName]];
NSString *response = [ZoomCatalogAppDelegate getResponseFromServer:#"http://www.zoomcatalog.com/iphone/supplier.php"];
//NSString *response = [ZoomCatalogAppDelegate getResponseFromServer:#"http://test.atvescape.com/articles.php"];
//NSLog(#"Response = %#", response);
NSArray *array = [response componentsSeparatedByString:#"##"];
[array retain];
for(int i = 0; i < array.count; i++)
{
NSLog(#"Trying outer loop.... %d, %#, %#", i, [array objectAtIndex:i], featuredCatalogName);
NSArray *array4 = [featuredCatalogName componentsSeparatedByString:[array objectAtIndex:i]];
if(array4.count > 1)
{
response = [ZoomCatalogAppDelegate getResponseFromServer:[NSString stringWithFormat:#"http://www.zoomcatalog.com/iphone/catalog_search.php?tid2=%#&iphone=yes", [array objectAtIndex:i]]];
NSArray *array3= [response componentsSeparatedByString:#"<br>"];
//baseURL = [NSString stringWithFormat:#"%#", [array3 objectAtIndex:0]];
global_ContentString = [NSString stringWithFormat:#"%#", [array3 objectAtIndex:2]];//(searchId == 1 ? [array objectAtIndex:2] : ([array objectAtIndex: isLineNameSearch ? 2 : 1]))];
[global_ContentString retain];
// NSLog(#"baseURL = %#", global_ContentString);
NSArray *array1 = [global_ContentString componentsSeparatedByString:#"###"];
for(int j = 0; j < array1.count; j++)
{
NSArray *array2 = [[array1 objectAtIndex:j] componentsSeparatedByString:#"##"];
NSString *str = [NSString stringWithFormat:#"%#", [array2 objectAtIndex:0]];
str = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([str caseInsensitiveCompare:featuredCatalogName] == NSOrderedSame)
{
global_ContentString = [ZoomCatalogAppDelegate getResponseFromServer:[NSString stringWithFormat:#"http://www.zoomcatalog.com/iphone/iphone.php?catalog=%#&iphone=Yes&pdf=No", [array2 objectAtIndex:5]]];
baseURL = [NSString stringWithFormat:#"%#", [[global_ContentString componentsSeparatedByString:#"<br>"] objectAtIndex:0]];
//global_ContentString = [NSString stringWithFormat:#"%#", [[global_ContentString componentsSeparatedByString:#"<br>"] objectAtIndex:1]];
[global_ContentString retain];
[global_MainPageController presentModalViewController:global_FullPageController animated:YES];
//NSLog(#"$$$$$$$$$$$$$$$$$$$$$$ Catalog id = %# $$$$$$$$$$$$$$$$$$$$$$$$$$", [array2 objectAtIndex:5]);
//[array1 release];memory leak
return;
}
// NSLog(#"Trying inner loop.... %d, %#, %#", j, str, featuredCatalogName);
}
}
// if([[array objectAtIndex:i] com
}
[array release];
return;
}
sorry for all..
If you are only using an object locally (within the method in which it is created) you can autorelease it. Objects that are created or returned by convenience methods available until the end of the function call. Unless you need the objects elsewhere, I suggest ditching the retain calls. The rule of thumb is that whenever you call alloc, new, retain, or copy you mist release the object. However, if you use a convenience method, The returned object is autogenerate for you.
It seems that you call [global_ContentString retain]; but then fail to call a corresponding release.

Memory leak in NSMutableArray allocation

I am getting memory leak in NSMutableArray allocation.. in
NSMutableArray *contactsArray =[[NSMutableArray alloc] init];
CODE:
+(NSMutableArray*)getContacts
{
addressBook = ABAddressBookCreate();
NSArray* peopleArray = (NSArray*) ABAddressBookCopyArrayOfAllPeople(addressBook);
int noOfPeople = [peopleArray count];
NSMutableArray *contactsArray =[[NSMutableArray alloc] init];
for ( int i = 0; i < noOfPeople; i++)
{
ABRecordRef person = [peopleArray objectAtIndex:i];
ABRecordID personId = ABRecordGetRecordID(person);
NSString* personIdStr = [NSString stringWithFormat:#"%d", personId];
ContactDTO* contactDTO = [AddressBookUtil getContactDTOForId:personIdStr];
[contactsArray addObject:contactDTO];
}
[peopleArray release];
return contactsArray;
}
It is standard procedure that objects returned from methods (in your case, contactsArray) are autoreleased before returning.
You could either return [contactsArray autorelease]; or create it already autoreleased with [NSMutableArray arrayWithCapacity:noOfPeople]
You need to release contactsArray manually somewhere, because it does not define autorelease.