I am having a problem with what I believe is a memory leak which after some time is causing my app to slow down. I have a sectioned uitableview that has sections of 'movie directors' with rows of thier movies in their particular section. To do this I am calling a data object (and passing it the section header) to return that section's data and populate the section rows. So I am calling that object a few times on the same view(numberOfRowsInSection, cellForRowAtIndexPath, and didSelectRowAtIndexPath) this happens for each section. Looking at Instruments, I believe the leak is coming from getDirectorsMovies:theDirector from Movies.m. Can anyone tell me what I am doing that is causing this leak. Any help would be greatly appreciated, I've been working on this for a few weeks. Below is some code to show what I am doing.
Thanks in advance!!!
//Movies.h
#import <Foundation/Foundation.h>
#import <sqlite3.h>
#import "Movie.h"
#interface Movies : NSObject {
}
- (NSMutableArray *) getDirectorsMovies:(NSString *)theDirector;
#end
//Movies.m //getDirectorsMovies:(NSString *)theDirector goes to the database, gets the directors movies, and returns them in an array
#import "Movies.h"
#implementation Movies
- (NSMutableArray *) getDirectorsMovies:(NSString *)theDirector
{
sqlite3 *database;
NSString *databaseName = #"Movies.sql";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
NSMutableArray *theDirectorsMovies = [[NSMutableArray alloc] init];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = "select * from movies where lastname = ? order by lastname, movie";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
sqlite3_bind_text(compiledStatement, 1, [theDirector UTF8String], -1, SQLITE_TRANSIENT);
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSString *aLastName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *aDirector = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
NSString *aMovie = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 3)];
Movie *movie = [[Movie alloc] initWithName:aMovie lastname:aLastName director:aDirector];
[theDirectorsMovies addObject:movie];
[movie release];
}
}
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return theDirectorsMovies;
[theDirectorsMovies release];
}
#end
//Calling getDirectorsMovies:(NSString *)theDirector
MoviesAppDelegate *appDelegate = (MoviesAppDelegate *)[[UIApplication sharedApplication] delegate];
Director *director = (Director *)[appDelegate.director objectAtIndex:indexPath.section];//appDelegate.director IS A MSMutableArray defined in the AppDelegate
self.theMovies = nil;//THIS IS A MSMutableArray defined in the AppDelegate
Movies *directorMovies = [[Movies alloc] init];
self.theMovies = [directorMovies getDirectorMovies:director.lastname];
[directorMovies release];
Movie *movie = (Movie *)[theMovies objectAtIndex:indexPath.row];
//do whatever with the data
[movie release];
You have this:
return theDirectorsMovies;
[theDirectorsMovies release];
Nothing happens after the return statement, so your call to release will never happen. It was for this very reason that AutoreleasePools were invented (and patented). Simply do:
return [theDirectorsMovies autorelease];
And your memory leak will go away.
There are a number of potential issues here it would be useful to see the init method for Movie as that may be where the issue is. Also you should have an autorelease on the NSMutableArray.
In my sqlite code I have the following statement normally before the sqlite3_finalize call
sqlite3_clear_bindings(compiledStatement);
That call cleared up a lot of issues for my code.
Related
I have a database manager which successfully loads a string from my sqlite database, and then uses it to create an object. I add multiple of these objects to an array and return it to which ever class I'm calling it from.
I have no problem using the returned array in the method I call it from, but when other methods in the class try to access it, the string element is out of scope.
Here is my relavent code.
In DBManager.m:
-(NSArray *)loadObjectData {
NSMutableArray *myArray = [[NSMutableArray alloc] init];
//prepare query and statement
NSString *query = [NSString stringWithFormat:#"SELECT * FROM object;"];
sqlite3_stmt *statement;
//perform query
if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) != SQLITE_OK)
NSLog(#"Prepare Error. '%s'", sqlite3_errmsg(database));
//get data
while (sqlite3_step(statement) == SQLITE_ROW) {
//read values
int anInt = sqlite3_column_int(statement, 0);
NSString *aString = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
//add new object to list
[myArray addObject:[[Object alloc] initWithID:anInt Name:aString]];
}
if(sqlite3_finalize(statement) != SQLITE_OK)
NSLog(#"Finalize statement error.");
return [myArray autorelease];
}
In XViewController.h:
NSArray *listData;
SessionData *sessionData;
#property (nonatomic, retain) NSArray *listData;
#property (nonatomic, retain) SessionData *sessionData;
In XViewController.m:
- (void)viewDidLoad
{
dbMan = [DBManager sharedDBMananager]; //its a singleton
//Load objects to display in table
self.listData = [dbMan loadObjectData];
//if i try to use listData here, it works fine.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//normal stuff cut out
// Configure the cell...
//the following line fails because the string in the object out of scope
cell.textLabel.text = [[listData objectAtIndex:[indexPath row]] aString];
//trying to access [[listData objectAtIndex:[indexPath row]] anInt] works fine though.
return cell;
}
If I replace the line (in my loadObjectData method):
NSString *aString = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
with this:
NSString *aString = #"String";
everything works fine.
Take a look at this question's accepted answer - What does assigning a literal string to an NSString with "=" actually do?
The string literal ( #"String" ) works because it is statically allocated and does not need to be retained/released. [NSString stringWithUTF8String:] creates an autoreleased string. If you are not retaining it in your initWithID:Name: method then it is being released which is why your code isn't working. In your initWithID:Name: method at a retain when you reassign the name to your object's instance variable.
In Object class have you retained this string if not then i think you should and release this string in dealloc
if I try and reun the code below I get an EXE_bad_access message on [categoryList count]
NSMutableArray *categoryList = [[CategoryItem alloc] getAll];
NSLog(#"number of items is %#", [categoryList count]);
The class is below
#import "CategoryItem.h"
#import "SQLite.h"
#interface CategoryItem : NSObject {
NSInteger ID;
NSInteger SortOrder;
NSString *Name;
NSString *ShoppingImage;
}
#property (nonatomic, nonatomic) NSInteger SortOrder;
#property (nonatomic, retain) NSString * Name;
#property (nonatomic, retain) NSString * ShoppingImage;
#property (nonatomic, nonatomic) NSInteger ID;
- (id)initWithObject:(NSInteger)itemID;
-(NSMutableArray *)getAll;
#end
#implementation CategoryItem
#synthesize ShoppingImage;
#synthesize Name;
#synthesize ID;
#synthesize SortOrder;
- (id)initWithObject:(NSInteger)itemID {
if ((self = [super init])) {
sqlite3 *database;
// Open the database. The database was prepared outside the application.
if (sqlite3_open([[SQLite fullFilePath] UTF8String], &database) == SQLITE_OK) {
// Get the primary key for all books.
const char *sql = "SELECT ID, Name, ShoppingImage, SortOrder FROM CategoryItem WHERE ID =?";
sqlite3_stmt *statement;
// Preparing a statement compiles the SQL query into a byte-code program in the SQLite library.
// The third parameter is either the length of the SQL string or -1 to read up to the first null terminator.
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK) {
// We "step" through the results - once for each row.
sqlite3_bind_int(statement, 1, itemID);
while (sqlite3_step(statement) == SQLITE_ROW) {
// The second parameter indicates the column index into the result set.
self.ID = itemID;
self.Name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
self.ShoppingImage = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
self.SortOrder = sqlite3_column_int(statement, 3);
}
}
// "Finalize" the statement - releases the resources associated with the statement.
sqlite3_finalize(statement);
} else {
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(database);
NSLog(#"Failed to open database with message '%s'.", sqlite3_errmsg(database));
// Additional error handling, as appropriate...
}
}
return self;
}
-(NSMutableArray*)getAll{
NSMutableArray *listArray = [[[NSMutableArray alloc] init] autorelease];
sqlite3 *database;
// Open the database. The database was prepared outside the application.
if (sqlite3_open([[SQLite fullFilePath] UTF8String], &database) == SQLITE_OK) {
// Get the primary key for all books.
const char *sql = "SELECT ID, Name, ShoppingImage, SortOrder FROM CategoryItem ORDER BY SortOrder";
sqlite3_stmt *statement;
// Preparing a statement compiles the SQL query into a byte-code program in the SQLite library.
// The third parameter is either the length of the SQL string or -1 to read up to the first null terminator.
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK)
{
// We "step" through the results - once for each row.
while (sqlite3_step(statement) == SQLITE_ROW)
{
// The second parameter indicates the column index into the result set.
CategoryItem *categoryItem = [[CategoryItem alloc] init];
categoryItem.ID = sqlite3_column_int(statement, 0);
categoryItem.Name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
categoryItem.ShoppingImage = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
categoryItem.SortOrder = sqlite3_column_int(statement, 3);
[listArray addObject:categoryItem];
[categoryItem release];
categoryItem = nil;
}
}else{
printf( "could not prepare statemnt: %s\n", sqlite3_errmsg(database) );
}
// "Finalize" the statement - releases the resources associated with the statement.
sqlite3_finalize(statement);
} else {
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(database);
NSLog(#"Failed to open database with message '%s'.", sqlite3_errmsg(database));
// Additional error handling, as appropriate...
}
//NSLog(#"this is the list array count %#", [listArray count]);
return listArray;
}
- (void)dealloc {
[super dealloc];
[Name release];
[ShoppingImage release];
}
#end
It doesn't seem right the way you create your CategoryItem. You are calling allocbut not any init... method. You may want to use the initWithObject method that you have provided in your implementation.
From Apple docs:
It takes two steps to create an object
using Objective-C. You must:
Dynamically allocate memory for the
new object
Initialize the newly
allocated memory to appropriate values
An object isn’t fully functional until
both steps have been completed. Each
step is accomplished by a separate
method but typically in a single line
of code:
id anObject = [[Rectangle alloc]
init];
EDIT:
Beyond the initialization problem, there seems to be a conceptual problem (pointed out by #Terry Wilcox):
Calling the method getAllon an instance does not seem to make sense and therefore should be defined as a class method instead:
+ (NSMutableArray*)getAll;
and should be called like this:
NSMutableArray *categoryList = [CategoryItem getAll];
EDIT 2:
Your log statement does not seem right either. [categoryList count]returns a NSUIntegerand you are trying to print an object with %#. Use %iinstead:
NSLog(#"number of items is %i", [categoryList count]);
This code:
NSMutableArray *categoryList = [[CategoryItem alloc] getAll];
doesn't make sense. If getAll is a class method on CategoryItem, then it should be defined as
+ (NSMutableArray*)getAll;
and you should call it as
NSMutableArray *categoryList = [CategoryItem getAll];
Then categoryList will be an array that you don't own, so you may want to retain it when you get it.
I have an NSObject that I am using to store/hold properties of an object, one of them being a "Name" property cast as an NSString. I am also pulling data from a SQLite DB for said object using the following:
- (void) getDataToDisplay:(NSString *)dbPath {
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {
NSString *queryOnStyle = [NSString stringWithFormat:
#"SELECT WineId, Name FROM wine WHERE StyleId = %d", dataManager.styleId];
const char *sql = [queryOnStyle UTF8String];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) {
while(sqlite3_step(selectstmt) == SQLITE_ROW) {
Wine *w = [[Wine alloc] init];
w.wineId = sqlite3_column_int(selectstmt, 0);
w.wineName = [NSString stringWithUTF8String:
(char *)sqlite3_column_text(selectstmt, 1)];
[dataManager.wines addObject:w];
[w release];
}
}
}
else
sqlite3_close(database); //Even though the open call failed, close the database connection to release all the memory.
}
Wine being my object. If I were to log w.wineName at this point, there is no problem. The problem occurs later when I try to access the properties of the object from within the array, dataManager.wines, in a custom tableView. It suddenly treats my wineName as a UIImageView rather than an NSString...
I for the life of me cannot trace anything back to ever being cast as a UIImageView and have no idea why it would be setting just that property as such. Here is the code for my custom tableView:
#pragma mark -
#pragma mark HorizontalTableViewDelegate methods
- (NSInteger)numberOfColumnsForTableView:(HorizontalTableView *)tableView {
return [dataManager.wines count];
}
- (UIView *)tableView:(HorizontalTableView *)aTableView viewForIndex:(NSInteger)index {
UIView *vw = [aTableView dequeueColumnView];
if (!vw) {
[[NSBundle mainBundle] loadNibNamed:#"ColumnView" owner:self options:nil];
vw = self.columnView;
self.columnView = nil;
}
// Get the wineId from the array of wineId integers
Wine *w = [dataManager.wines objectAtIndex:index];
int tempWineId = w.wineId;
NSString *tempWineName = [NSString stringWithFormat:#"%#", w.wineName];
NSLog(#"%#", tempWineName); \\RETURNS TEMPWINENAME AS A UIIMAGEVIEW
[w release];
return vw;
}
- (CGFloat)columnWidthForTableView:(HorizontalTableView *)tableView {
//TODO: This value needs to change if changed in IB
return 209.0f;
}
any ideas?
Resolved in comments of question by RichB:
is w.wineName a retain'ed property ? This sounds a bit like the string is being auto released. Could you post the definition of the Wine object?
NSString *strSql = #"select tblrecentsearch_id,xmlrequest,company,postcode,city,kilometer,date from tblrecentsearch";
returnValue = sqlite3_prepare_v2(database, [strSql UTF8String], -1, &selectStatement, NULL);
if(returnValue == SQLITE_OK)
{
arrRecentSearch=[[NSMutableArray alloc] init];
while(sqlite3_step(selectStatement)==SQLITE_ROW)
{
Search *ObjSearch = [[Search alloc]init];
ObjSearch.intRecentSearchId = sqlite3_column_int(selectStatement, 0);
ObjSearch.xmlRequest = [NSString stringWithCString:(char *)sqlite3_column_text_check(selectStatement, 1) encoding:NSUTF8StringEncoding];
ObjSearch.strCompnay=[NSString stringWithCString:(char *)sqlite3_column_text_check(selectStatement, 2) encoding:NSUTF8StringEncoding];
ObjSearch.strPostCode=[NSString stringWithCString:(char *)sqlite3_column_text_check(selectStatement, 3) encoding:NSUTF8StringEncoding];
ObjSearch.strPlace = [NSString stringWithCString:(char *)sqlite3_column_text_check(selectStatement, 4) encoding:NSUTF8StringEncoding];
ObjSearch.strKilometer = [NSString stringWithCString:(char *)sqlite3_column_text_check(selectStatement, 5) encoding:NSUTF8StringEncoding];
ObjSearch.strDate = [NSString stringWithCString:(char *)sqlite3_column_text_check(selectStatement, 6) encoding:NSUTF8StringEncoding];
[arrRecentSearch addObject:ObjSearch];
[ObjSearch release];
}
}
sqlite3_finalize(selectStatement);
I want release arrRecentSearch but it will return from function . How can i realese this array. Please help me.I am fetching data from databse.
just autorelease it :
return [arrRecentSearch autorelease];
Have a look at the apple memopry management guidelines for more information on how this works
If you are going to return an autoreleased object, you must remember to retain it if you wnat to keep it around later. i.e. if we have a function that returns an autoreleased array
- (NSArray *) getSearchResults {
return [[[NSArray alloc] init] autorelease];
}
and you want to remember the search results for later you must remember to do this :
...
NSArray *results = [[self getSearchResults] retain]; //!< Remember the retain here!
...
or, you might use a property to store it :
#property (nonatomic, copy) NSArray *searchResults;
...
self.searchResults = [self getSearchResults]; //!< The property handles the retain for you here
...
Either way, if you just leave it as autoreleased, it's going to vanish and you're going to get an exception!
EDIT: Just realised MustISignUp has answered this in a comment!
I was successfully accessing my database to get a list of cities on the App launch. I tried running a second query against it right afterward to get the list of States but all that happens is that my app blows up with no usable error in the console (simply says "Program received signal: EXEC_BAD_ACCESS" and nothing more).
Here is the code, I was hoping someone could potentially explain to me what I'm doing wrong:
-(void) initializeDatabase{
// The database is stored in the application bundle
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"mydatabase.sqlite"];
// Open the database. The database was prepared outside the application.
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK){
[self initializeCities:database];
[self initializeStates:database];
} else {
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(database);
NSAssert1(0, #"Failed to open database with message '%s'.", sqlite3_errmsg(database));
// Additional error handling, as appropriate...
}
}
-(void) initializeCities:(sqlite3 *)db {
NSMutableArray *cityArray = [[NSMutableArray alloc] init];
self.cities = cityArray;
[cityArray release];
// Get the primary key for all cities.
const char *sql = "SELECT id FROM my_table ORDER BY state";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(db, sql, -1, &statement, NULL) == SQLITE_OK){
while (sqlite3_step(statement) == SQLITE_ROW){
int primaryKey = sqlite3_column_int(statement, 0);
City *city = [[City alloc] initWithPrimaryKey:primaryKey database:db];
[cities addObject:city];
[city release];
}
}
// "Finalize" the statement - releases the resources associated with the statement.
sqlite3_finalize(statement);
}
-(void) initializeStates:(sqlite3 *)db {
NSMutableArray *statesArray = [[NSMutableArray alloc] init];
self.states = statesArray;
[statesArray release];
// Get the primary key for all cities.
const char *sql = "SELECT DISTINCT state FROM my_table ORDER BY state";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(db, sql, -1, &statement, NULL) == SQLITE_OK){
// We "step" through the results - once for each row
while (sqlite3_step(statement) == SQLITE_ROW){
NSString *state;
state = (NSString *)sqlite3_column_text(statement, 0);
[states addObject:state];
[state release];
}
}
// "Finalize" the statement - releases the resources associated with the statement.
sqlite3_finalize(statement);
}
I can't debug this code as the debugger never hits my breakpoints at all.
If I remove the initializeStates method the app works as expected (albiet without a list of states).
You are releasing "state" without having allocated it. Try something like this:
while (sqlite3_step(statement) == SQLITE_ROW){
NSString *state = [[NSString alloc] initWithCString:(char*)sqlite3_column_text(statement, 0) encoding:NSASCIIStringEncoding];
//state = (NSString *)sqlite3_column_text(statement, 0);
[states addObject:state];
[state release];
}
Update: add cast above to fix compiler warning
Your problem is this:
NSString *state = (NSString *)sqlite3_column_text(statement, 0);
According to the documentation, sqlite3_column_text() returns a char*, not an NSString*.
Edit: You wouldn't have had this problem if you'd have used a wrapper ;)