views:

55

answers:

4

Hi, I have searched high and low to try to resolve this memory leak, but I can't figure out how to make it go away. I have several classes that I use to connect to my sqlite dbase and pull information.

When I run the app using performance tools, leaks, I keep finding that all of my classes leak memory.

Below is the code that I have. Any help resolving this leak would be greatly appreciated.

tbl_materialsList.h

@interface tbl_materialsList : NSObject {

 NSInteger materialListID;
 NSString *shoppingListID;
 NSString *projectID;
 NSString *materialName;
 NSString *numberOfUnits;
 NSString *purchased;
 NSString *totalPrice;
 NSString *unitPrice;

}

@property (nonatomic, readonly) NSInteger materialListID;
@property (nonatomic, retain) NSString *shoppingListID;
@property (nonatomic, retain) NSString *projectID;
@property (nonatomic, retain) NSString *materialName;
@property (nonatomic, retain) NSString *numberOfUnits;
@property (nonatomic, retain) NSString *purchased;
@property (nonatomic, retain) NSString *totalPrice;
@property (nonatomic, retain) NSString *unitPrice;

- (id)getDataToDisplay:(NSString *)dbPath :(NSString *)selectStatement;
- (void)saveData:(NSString *)dbPath :(NSString *)selectStatement;
- (id)initWithPrimaryKey:(NSInteger)pk;

tbl_materialsList.m

- (id)getDataToDisplay:(NSString *)dbPath :(NSString *)selectStatement {


 // Init the data Array
 NSMutableArray *data = [[NSMutableArray alloc] init];

 if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {

  NSString *sql = selectStatement; //"select * from tbl_projects";
  sqlite3_stmt *selectstmt;
  if(sqlite3_prepare_v2(database, [sql UTF8String], -1, &selectstmt, NULL) == SQLITE_OK) {
   //loop thru and fill the array
   while(sqlite3_step(selectstmt) == SQLITE_ROW) {
    //reading the results
    NSInteger primaryKey = sqlite3_column_int(selectstmt, 0);
    tbl_materialsList *listObj = [[tbl_materialsList alloc] initWithPrimaryKey:primaryKey];
    listObj.shoppingListID = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 1)];
    listObj.projectID = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 2)];
    listObj.materialName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 3)];
    listObj.numberOfUnits = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 4)];
    listObj.purchased = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 5)];
    listObj.totalPrice = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 6)];
    listObj.unitPrice = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 7)];

    [data addObject:listObj];
    [listObj release];
   }
  }

  //release the compiled statment from memory
  sqlite3_finalize(selectstmt);

 }

 sqlite3_close(database); //Even though the open call failed, close the database connection to release all the memory.

 return data;

}
A: 

strings should be @property(nonatomic,copy) first of all, and you need to release data. [data release];

Sheehan Alam
Why do they need to be 'copy' and not 'retain'?
Graham Perks
@Graham - The only reason I have heard is because it is possible that you have pointed your retain at an NSMutableString that could then be changed out from under you. Copying prevents this. I'm not sure why people focus on this issue (which exists with any retained reference) specifically for NSString.
Peter
I hate to sound dumb, but if I release data after the return, it appears that nothing is happening. When I step through the code, it still has all of its objects. If I release before, it crashes as I would expect.
John Shehata
Do you mean at the end of the function you have something like: return; [data release];} (sorry for the formatting - comments are limited). Nothing after a return statement will ever be executed. You could just autorelease (NSMutableArray *data = [NSMutableArray array]
Peter
Sheehan is correct, they should be copy to help enforce encapsulation.
logancautrell
Peter: most types that conform to NSCopying should have their properties specified as copy. I imagine people mention NSString specifically because most Objective-C newcomers come from languages where the string type is immutable, but arrays and hashes/dictionaries are mutable.
rpetrich
+1  A: 

Does your tbl_materialslist implement a dealloc that sets all your retain properties to nil? If not, they are all leaking even though your listObj is being released.

Peter
Hi, thanks for the quick reply. I do the following... - (void)dealloc { [shoppingListID release]; [projectID release]; [materialName release]; [numberOfUnits release]; [purchased release]; [totalPrice release]; [unitPrice release]; [super dealloc];}
John Shehata
That looks fine to me. Sheehan is correct that you must also release data.
Peter
+3  A: 

Your final line:

return data;

should be:

return [data autorelease];

Unless, of course, you intend the returned data object to be owned by the caller, then you should make the method name conform to the Objective-C naming conventions in that method that return objects with a retain count of +1 should contain one of the words "copy" "create" or "new";

But I suspect that this isn't what you intend.

Jasarien
when I do return [data autorelease]; the app crashes. I am basically getting this data and loading it into an NSMutableArray and using it to display the material list in a table. When I look in the leak instrument, it shows 100% is at the call, and then when I go into this code, it shows different percentages in each of the listObj.materialName, itemCost, etc.
John Shehata
After making the [data autorelease] change, you also need to retain the resulting data object in the method that is calling getDataToDisplay: and then make sure it is release properly when you are done with it.
logancautrell
The call I am making is tbl_materialsList *dbaseTable = [[tbl_materialsList alloc] init]; NSString *statement = [NSString stringWithFormat:@"select * from tbl_materialsList where projectID = %d", projectData.projectID]; NSArray *data = [dbaseTable getDataToDisplay:[appDelegate getDBPath] :statement]; I always thought data would be autoreleased...
John Shehata
A: 

An unrelated advice:

Never return an object as (id) unless it's absolutely necessary. For example, your getDataToDisplay:: should be

-(NSMutableArray*)getDataToDisplay:(NSString *)dbPath :(NSString *)selectStatement;

That way, you can get more compiler warnings when you make a mistake.

I guess there's an iPhone programming book which promotes this bad custom. The author of the book should be leashed and the book should be burned ... :p

Yuji