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;
}
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.
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.
strings should be #property(nonatomic,copy) first of all, and you need to release data. [data release];
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
Related
I have made a very simple custom object pictureData.
Here is the .h file
#import <Foundation/Foundation.h>
#interface pictureData : NSObject {
NSString *fileName;
NSString *photographer;
NSString *title;
NSString *license;
}
#property (nonatomic, retain) NSString *fileName;
#property (nonatomic, retain) NSString *photographer;
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSString *license;
+(pictureData*)picDataWith:(NSDictionary*)dictionary;
#end
The .m file
#import "pictureData.h"
#implementation pictureData
#synthesize fileName;
#synthesize photographer;
#synthesize title;
#synthesize license;
+ (pictureData*)picDataWith:(NSDictionary *)dictionary {
pictureData *tmp = [[[pictureData alloc] init] autorelease];
tmp.fileName = [dictionary objectForKey:#"fileName"];
tmp.photographer = [dictionary objectForKey:#"photographer"];
tmp.title = [dictionary objectForKey:#"title"];
tmp.license = [dictionary objectForKey:#"license"];
return tmp;
}
-(void)dealloc {
[fileName release];
[photographer release];
[title release];
[license release];
}
#end
I then set up these objects in an array, like so:
NSString *path = [[NSBundle mainBundle] pathForResource:#"pictureLicenses" ofType:#"plist"];
NSArray *tmpDataSource = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *tmp = [[NSMutableArray alloc] init];
self.dataSource = tmp;
[tmp release];
for (NSDictionary *dict in tmpDataSource) {
pictureData *pic = [pictureData picDataWith:dict];
NSLog(#"%#", pic.title);
[self.dataSource addObject:pic];
}
Everything works smashingly. I have a table view which loads the proper picture images, and information, no problem. Upon running Instruments for leaks, I see that my pictureData object is leaks with every allocation.
I would assume that with having my object autoreleased I would not have to worry about manually allocating and deallocating them.
Perhaps is my issue that I use autorelease, which the autoReleasePool keeps a retain count of +1 and then when I add a pictureData object to my array, that also retains it? Thank you all for your time!
edit: Don't forget to call super! Thank you Sam!
Change dealloc to:
-(void)dealloc {
[fileName release];
[photographer release];
[title release];
[license release];
[super dealloc];
}
(call [super dealloc])
In your function, change the return value to include autorelease, like
+ (pictureData*)picDataWith:(NSDictionary *)dictionary
{
...
...
return [tmp autorelease];
}
When you add pictureData object to dataSource, you increase the retain count, so you should autorelease it while returning.
Hope it helps.
I am trying to do some basic table retrieval from sqlite and I keep stumbling over some memory leak I cannot figure out, especially since it appears only with one of two equivalent lines.
What I do is simple :
-Create an object (category) to hold my sqlite DB line
-Have a method that queries sqlite and returns an array of these objetcs
I put all the code here (stripped it down a bit to simplify but the I have the memory leak with this very code).
category.h:
#interface Category : NSObject {
int catId;
NSString *description;
int order;
NSString *iconName;
}
#property(nonatomic, readwrite, assign) int catId;
#property(nonatomic, retain) NSString *description;
#property(nonatomic, readwrite, assign) int order;
#property(nonatomic, retain) NSString *iconName;
#end
category.m:
#import "Category.h"
#implementation Category
#synthesize catId;
#synthesize description;
#synthesize order;
#synthesize iconName;
#end
Method to retrieve array:
-(NSMutableArray *)getAll {
NSString *query = "select id, description, catorder, image from category order by catorder";
sqlite3_stmt *statement;
NSMutableArray *categoryArray = [[NSMutableArray alloc] init];
if (sqlite3_prepare_v2([[Database sharedDatabase] instance], [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
int dbId = sqlite3_column_int(statement, 0);
char *dbDescription = (char *)sqlite3_column_text(statement, 1);
int dbOrder = sqlite3_column_int(statement, 2);
char *dbIcon = (char *)sqlite3_column_text(statement, 3);
NSString *desc = [[NSString alloc] initWithUTF8String:dbDescription];
NSString *icon = [[NSString alloc] initWithUTF8String:dbIcon];
Category *category = [[Category alloc] init];
category.catId=dbId;
category.description = desc;
//category.description = #"Test";
category.order = dbOrder;
category.iconName = icon;
//category.icon = #"test.png";
[desc release];
[icon release];
[categoryArray addObject:category];
[category release];
}
sqlite3_finalize(statement);
}
[categoryArray autorelease];
return categoryArray;
}
This line leaks
NSString *icon = [[NSString alloc] initWithUTF8String:dbIcon];
This line doesn't
NSString *desc = [[NSString alloc] initWithUTF8String:dbDescription];
Here what instruments shows :
and when I drill down :
You don't have a dealloc method. You need to have this in your Category.m file:
-(void)dealloc
{
[description release];
[iconName release];
[super dealloc];
}
Also, I'm pretty sure you can't have a property named "description". That's a reserved method name.
I have been getting this error and have no idea how to fix it. I am a newbie for iOS programming.
Hope the codes below is enough to see what the problem is.
Here is my ListTableController.m
...
#implementation ListTableController
#synthesize listTableView;
#synthesize detailController;
#synthesize listOfTasks;
#synthesize title, note, date;
-(id)initWithTitle:(NSString *)sTitle note:(NSString *)sNote date:(NSString *)sDate {
NSLog(#"FUNC: %s",__FUNCTION__);
self.title = sTitle;
self.note = sNote;
self.date = sDate;
return self;
}
// Retrieve records
-(void) getAllRowsFromTableNamed: (NSString *) tableName {
//---retrieve rows---
NSString *qsql = [NSString stringWithFormat:#"SELECT * FROM %# WHERE status = '1' ORDER BY date ASC",tableName]; sqlite3_stmt *statement;
//---initialize the array---
listOfTasks = [[NSMutableArray alloc] init];
if (sqlite3_prepare_v2( db, [qsql UTF8String], -1, &statement, nil) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *fieldTitle = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSString *fieldNote = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
NSString *fieldDate = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 3)];
// Create a new object with the data from the database
NSString *str = [[NSString alloc] initWithTitle :fieldTitle note:fieldNote date:fieldDate];
[listOfTasks addObject:str];
[str release];
[fieldTitle release];
[fieldNote release];
[fieldDate release];
}
//---deletes the compiled statement from memory---
sqlite3_finalize(statement);
}
}
...
Here is my ListTableController.h
#import
#import "sqlite3.h"
#import "TaskDetailController.h"
#interface ListTableController : UITableViewController {
IBOutlet UITableView *listTableView;
sqlite3 *db;
TaskDetailController *detailController;
}
NSString *title;
NSString *note;
NSString *date;
NSMutableArray * listOfTasks;
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSString *note;
#property (nonatomic, retain) NSString *date;
#property (nonatomic, retain) TaskDetailController *detailController;
#property (nonatomic, retain) IBOutlet UITableView *listTableView;
#property (nonatomic, retain) NSMutableArray *listOfTasks;
-(void) copyDatabaseIfNeeded;
-(NSString *) filePath;
-(IBAction)addCalendar;
-(NSInteger)countToday;
-(id)initWithTitle:(NSString *)sTitle note:(NSString *)sNote date:(NSString *)sDate;
#end
And this is the error log from the console:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSPlaceholderString initWithTitle:note:date:]: unrecognized selector sent to instance 0x4e00470'
Try removing these lines. Class methods as [NSString stringWith.... returns autoreleased objects.
[fieldTitle release];
[fieldNote release];
[fieldDate release];
I think you have to learn about memory management first. You will get a big headache if you don't.
Try removing these lines.
[fieldTitle release];
[fieldNote release];
[fieldDate release];
And if you want to combine title , note , and date as a single line.
then try this
// Create a new object with the data from the database
NSString *str = [[NSString stringWithFormate:#"%# %# %#",fieldTitle,fieldNote,fieldDate];
instead of
// Create a new object with the data from the database
NSString *str = [[NSString alloc] initWithTitle :fieldTitle note:fieldNote date:fieldDate];
I have the following function but I don't know how to release the memory of the temporary object defined:
#import <UIKit/UIKit.h>
#interface PatientsSet : NSObject {
NSString *tableid;
NSString *patient_name;
NSString *patient_surname;
NSString *city;
NSString *State;
NSString *phone;
}
#property (nonatomic, retain) NSString *tableid;
#property (nonatomic, retain) NSString *patient_name;
#property (nonatomic, retain) NSString *patient_surname;
#property (nonatomic, retain) NSString *city;
#property (nonatomic, retain) NSString *State;
#property (nonatomic, retain) NSString *phone;
-(id)initWithSet:(NSString *)dd patient_name:(NSString *)dn patient_surname:(NSString *)dsn city:(NSString *)ct state:(NSString *)st phone:(NSString *)ph ;
#end
(I'm getting from the a SQLITE DB the data into a NSobject derived class)
Shouldn't I use a [set release]; somewhere??
-(PatientsSet *)getPatientById:(NSString *)ID {
PatientsSet *set;
// Setup the database object
sqlite3 *database;
// Init the doctors_set Array
doctors_set = [[NSMutableArray alloc] init];
//NSString * databasePath1=[ [self getDocumentsDirectory] stringByAppendingPathComponent:databaseName ];
// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
// Setup the SQL Statement and compile it for faster access
NSString* myString = [NSString stringWithFormat:#"SELECT patients.id,patients.patient_name,patients.patient_surname,patients.phone FROM patients where patients.id = '%#'",ID];
const char *sqlStatement = [myString cString];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Read the data from the result row
NSString *aId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 0)];
NSString *aName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *aDurname = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
NSString *aPhone = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 3)];
// Create a new set object with the data from the database
set=[[PatientsSet alloc] initWithSet:aId patient_name:aName patient_surname:aDurname city:#"" state:#"" phone:aPhone];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return set;
}
You need to return an autoreleased object like so:
return [set autorelease];
I have declared a NSMutableArray and I populated it with information from the database. The table displays information well. But when I scroll down, the application exits. Seems like its loosing the pointer to the array.
Here is the code for declaration of the array:
#interface RootViewController : UITableViewController <CLLocationManagerDelegate> {
sqlite3 *database;
NSMutableArray *storeList;
CLLocationManager *locationManager;
CLLocation *startingPoint;
}
#property (nonatomic, retain) NSMutableArray *storeList;
#property (nonatomic, retain) CLLocationManager *locationManager;
#property (nonatomic, retain) CLLocation *startingPoint;
- (void) createCopyOfDatabaseIfNeeded;
- (void) initializeStoreList;
- (void) getDistanceFromUserLocation;
Here I am initializing the array with object of type StoreInfo:
- (void) initializeStoreList{
self.storeList = [[NSMutableArray alloc] init];
//database is stored in the application bundle.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dbPath = [documentsDirectory stringByAppendingPathComponent:kFileName];
if (sqlite3_open([dbPath UTF8String], &database)== SQLITE_OK) {
const char *sql = "select id, storename, ratings, lattitude, longitude from storeinformation";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
NSInteger *_pk = (NSInteger *) sqlite3_column_int(statement, 0);
NSString *_storeName = [NSString stringWithUTF8String:(char*) sqlite3_column_text(statement, 1)];
NSString *_ratings = [NSString stringWithUTF8String:(char*) sqlite3_column_text(statement, 2)];
double _lattitude = [[NSString stringWithUTF8String:(char*) sqlite3_column_text(statement, 3)] doubleValue];
double _longitude = [[NSString stringWithUTF8String:(char*) sqlite3_column_text(statement, 4)] doubleValue];
StoreInfo *si = [[StoreInfo alloc] initWithBasicInformation:_pk storeName:_storeName ratings:_ratings lattitude:_lattitude longitude:_longitude];
[self.storeList addObject:si];
[si release];
}
}
sqlite3_finalize(statement);
} else {
sqlite3_close(database);
NSAssert1(0,#"Failed to open the database with message '%s'.", sqlite3_errmsg(database));
}
}
here is the constructor for StoreInfo object
-(id)initWithBasicInformation:(NSInteger *)_pk storeName:(NSString *) _storeName ratings:(NSString *) _ratings lattitude:(double) _lattitude longitude:(double) _longitude;
{
if (self = [super init]) {
self.primaryKey = _pk;
self.storeName = _storeName;
self.ratings = _ratings;
self.lattitude = _lattitude;
self.longitude = _longitude;
}
return self;
}
Here is the code for displaying the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell.
StoreInfo *si = (StoreInfo *)[self.storeList objectAtIndex:indexPath.row];
cell.textLabel.text = si.storeName;
return cell;
}
The table displays alright first. But when I scroll down, this even gets fired and somehow it is not able to find reference to the si.storeName.
I have spent hours trying to debug the issue. Any help is greatly appreciated.
First of all, how have you defined the property for the problematic field?
Is it retain?
Secondly, Can you access any other property in si?
And finally, I see that there is a memory leak in self.storeList = [[NSMutableArray alloc] init]; - the object is retained twice (in init and in the property setter)...