Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

dean1012

macrumors regular
Original poster
Jul 10, 2008
130
1
Hi all,

I'm following the sqlitebooks example to implement that functionality into my application.

My data object only has two variables - primaryKey (integer) and pathName (NSString). Because of this, I have removed unneeded methods such as hydrate and dehydrate.

The PathListDB class represents a single row in the pathlist table.
The app delegate loads each row from the database into a PathListDB object and places that object into a NSMutableArray, paths.
The SubpathPathViewController contains a table view that is supposed to display the data from the app delegate's NSMutableArray paths.

From a series of NSLog statements, I know that the paths array in the app delegate is being loaded correctly. The primaryKey and pathName can be printed out from every object in the array.

However, if I try to access an object in this array from anywhere else in the application, ONLY primaryKey will display. Any attempt to display pathName results in a crash.

I'll provide some code below:

PathListDB.h & .m

Code:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <sqlite3.h>

@interface PathListDB : NSObject {
    // Opaque reference to the underlying database.
    sqlite3 *database;
    // Primary key in the database.
    NSInteger primaryKey;
    // Attributes.
    NSString *pathName;
}

// Property exposure for primary key and other attributes. The primary key is 'assign' because it is not an object, 
// nonatomic because there is no need for concurrent access, and readonly because it cannot be changed without 
// corrupting the database.
@property (assign, nonatomic, readonly) NSInteger primaryKey;
// The remaining attributes are copied rather than retained because they are value objects.
@property (copy, nonatomic) NSString *pathName;

// Finalize (delete) all of the SQLite compiled queries.
+ (void)finalizeStatements;

// Creates the object with primary key and title is brought into memory.
- (id)initWithPrimaryKey:(NSInteger)pk database:(sqlite3 *)db;

@end

#import "PathListDB.h"

// Static variables for compiled SQL queries. This implementation choice is to be able to share a one time
// compilation of each query across all instances of the class. Each time a query is used, variables may be bound
// to it, it will be "stepped", and then reset for the next usage. When the application begins to terminate,
// a class method will be invoked to "finalize" (delete) the compiled queries - this must happen before the database
// can be closed.
static sqlite3_stmt *init_statement = nil;

@implementation PathListDB

// Finalize (delete) all of the SQLite compiled queries.
+ (void)finalizeStatements {
    if (init_statement) sqlite3_finalize(init_statement);
}

// Creates the object with primary key and title is brought into memory.
- (id)initWithPrimaryKey:(NSInteger)pk database:(sqlite3 *)db {
    if (self = [super init]) {
        primaryKey = pk;
        database = db;
        // Compile the query for retrieving book data. See insertNewBookIntoDatabase: for more detail.
        if (init_statement == nil) {
            // Note the '?' at the end of the query. This is a parameter which can be replaced by a bound variable.
            // This is a great way to optimize because frequently used queries can be compiled once, then with each
            // use new variable values can be bound to placeholders.
            const char *sql = "SELECT name FROM pathlist WHERE id=?";
            if (sqlite3_prepare_v2(database, sql, -1, &init_statement, NULL) != SQLITE_OK) {
                NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
            }
        }
        // For this query, we bind the primary key to the first (and only) placeholder in the statement.
        // Note that the parameters are numbered from 1, not from 0.
        sqlite3_bind_int(init_statement, 1, primaryKey);
        if (sqlite3_step(init_statement) == SQLITE_ROW) {
			
            pathName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 0)];
        }
		
        // Reset the statement for future reuse.
        sqlite3_reset(init_statement);
    }
    return self;
}

- (void)dealloc {
    [pathName release];
    [super dealloc];
}

#pragma mark Properties
// Accessors implemented below. All the "get" accessors simply return the value directly, with no additional
// logic or steps for synchronization. The "set" accessors attempt to verify that the new value is definitely
// different from the old value, to minimize the amount of work done. Any "set" which actually results in changing
// data will mark the object as "dirty" - i.e., possessing data that has not been written to the database.
// All the "set" accessors copy data, rather than retain it. This is common for value objects - strings, numbers, 
// dates, data buffers, etc. This ensures that subsequent changes to either the original or the copy don't violate 
// the encapsulation of the owning object.

- (NSInteger)primaryKey {
    return primaryKey;
}

- (NSString *)pathName {
    return pathName;
}

- (void)setPathName:(NSString *)aString {

	pathName = aString;
	
}

@end

NexusReferenceAppDelegate .h & .m

Code:
#import <UIKit/UIKit.h>
// This includes the header for the SQLite library.
#import <sqlite3.h>

// Inform the compiler that the following classes are defined in the project:
@class PathListDB, SubpathTabViewController, LocationCityViewController, AboutViewController, SubpathPathViewController;
@class CaveChartLocationViewController, SpellListPathViewController, LocationListViewController, SubpathListViewController;
@class CaveChartListViewController, SpellListListViewController, LocationDetailViewController, SubpathDetailViewController;
@class CaveChartDetailViewController, SpellListDetailViewController;

@interface NexusReferenceAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
    IBOutlet UIWindow *window;
    IBOutlet UITabBarController *tabBarController;
    NSMutableArray *paths;
    // Opaque reference to the SQLite database.
    sqlite3 *database;
}

- (void)createEditableCopyOfDatabaseIfNeeded;
- (void)initializeDatabase;

@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) UITabBarController *tabBarController;

// Makes the main array of book objects available to other objects in the application.
@property (nonatomic, retain) NSMutableArray *paths;

@end

#import "NexusReferenceAppDelegate.h"
#import "PathListDB.h"

@implementation NexusReferenceAppDelegate;

// Instruct the compiler to create accessor methods for the property. It will use the internal 
// variable with the same name for storage.
@synthesize paths, window, tabBarController;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    // The application ships with a default database in its bundle. If anything in the application
    // bundle is altered, the code sign will fail. We want the database to be editable by users, 
    // so we need to create a copy of it in the application's Documents directory.     
    [self createEditableCopyOfDatabaseIfNeeded];
    // Call internal method to initialize database connection
    [self initializeDatabase];
    // Add the tab bar controller's view to the window
    [window addSubview:tabBarController.view];
    [window makeKeyAndVisible];
	/*
	NSLog(@"app delegate");
	int i = 0;
	for (i = 0; i < paths.count; i++)
	{
		PathListDB *tempPath = (PathListDB *)[paths objectAtIndex:i];
		NSLog(@"ID: %i | Text: %@", tempPath.primaryKey, tempPath.pathName);
	}
	 */
}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    // "dehydrate" all data objects - flushes changes back to the database, removes objects from memory
	
}

- (void)dealloc {
	[paths release];
	[tabBarController release];
    [window release];
    [super dealloc];
}

// Creates a writable copy of the bundled default database in the application Documents directory.
- (void)createEditableCopyOfDatabaseIfNeeded {
    // First, test for existence.
    BOOL success;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;
    NSArray *fpaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [fpaths objectAtIndex:0];
    NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"nexusdb.db"];
    success = [fileManager fileExistsAtPath:writableDBPath];
    if (success) return;
    // The writable database does not exist, so copy the default to the appropriate location.
    NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"nexusdb.db"];
    success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
    if (!success) {
        NSAssert1(0, @"Failed to create writable database file with message '%@'.", [error localizedDescription]);
    }
}

// Open the database connection and retrieve minimal information for all objects.
- (void)initializeDatabase {
	
	// pathlist table
    NSMutableArray *pathArray = [[NSMutableArray alloc] init];
    self.paths = pathArray;
    [pathArray release];
    // The database is stored in the application bundle. 
    NSArray *fpaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [fpaths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"nexusdb.db"];
    // Open the database. The database was prepared outside the application.
    if (sqlite3_open([path UTF8String], &database) == SQLITE_OK) {
        // Get the primary key for all books.
        const char *sql = "SELECT id FROM pathlist";
        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.
                int primaryKey = sqlite3_column_int(statement, 0);
                // We avoid the alloc-init-autorelease pattern here because we are in a tight loop and
                // autorelease is slightly more expensive than release. This design choice has nothing to do with
                // actual memory management - at the end of this block of code, all the book objects allocated
                // here will be in memory regardless of whether we use autorelease or release, because they are
                // retained by the books array.
                PathListDB *myPath = [[PathListDB alloc] initWithPrimaryKey:primaryKey database:database];
                [paths addObject:myPath];
                [myPath release];
            }
        }
        // "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);
        NSAssert1(0, @"Failed to open database with message '%s'.", sqlite3_errmsg(database));
        // Additional error handling, as appropriate...
    }
}

// Save all changes to the database, then close it.
- (void)applicationWillTerminate:(UIApplication *)application {
    // Dehydrate

	// finalize statements
    [PathListDB finalizeStatements];
    // Close the database.
    if (sqlite3_close(database) != SQLITE_OK) {
        NSAssert1(0, @"Error: failed to close database with message '%s'.", sqlite3_errmsg(database));
    }
}

@end

SubpathPathViewController .h & .m

Code:
#import <UIKit/UIKit.h>
#import "SubpathListViewController.h"
//#import "PathListDB.h"

@interface SubpathPathViewController : UIViewController {

	IBOutlet UITableView *tblView;
	SubpathListViewController *subpathListViewController;
	//PathListDB *myPathListDB;

}

@property (nonatomic, retain) UITableView *tblView;
@property (nonatomic, retain) SubpathListViewController *subpathListViewController;

@end

#import "SubpathPathViewController.h"
#import "PathListDB.h"
#import "NexusReferenceAppDelegate.h"

@implementation SubpathPathViewController

@synthesize subpathListViewController;
@synthesize tblView;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
	if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
		// Initialization code
	}
	return self;
}

/*
 Implement loadView if you want to create a view hierarchy programmatically
- (void)loadView {
}
 */

// If you need to do additional setup after loading the view, override viewDidLoad.
- (void)viewDidLoad {
	//myPathListDB = [PathListDB alloc];
	
	//[myPathListDB loadFromDB];
	
	self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
	
}

-(void)viewWillAppear:(BOOL)animated {

	[tblView reloadData];
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section {
	NexusReferenceAppDelegate *appDelegate = (NexusReferenceAppDelegate *)[[UIApplication sharedApplication] delegate];
	return appDelegate.paths.count;
	//return [myPathListDB getCount];
	
}

- (UITableViewCell *)tableView:(UITableView *)tableView
		 cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	static NSString *identity = @"MainCell";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identity];
	if (cell == nil)
	{
		cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
									   reuseIdentifier:identity] autorelease];
	}
	
	NexusReferenceAppDelegate *appDelegate = (NexusReferenceAppDelegate *)[[UIApplication sharedApplication] delegate];

	PathListDB *tempPath = (PathListDB *)[appDelegate.paths objectAtIndex:indexPath.row];

        // CRASH CRASH CRASH CRASH ------------------------------------------
	cell.text = tempPath.pathName;

	//cell.text = [myPathListDB getPathFromIndex:indexPath.row];

	return cell;
}

-(void)tableView:(UITableView *)tblView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
	[self.tblView deselectRowAtIndexPath:indexPath animated:YES];
	
	//Initialize the controller.
	if(subpathListViewController == nil)
		self.subpathListViewController = [[SubpathListViewController alloc] initWithNibName:@"SubpathListView" bundle:[NSBundle mainBundle]];
	
	NexusReferenceAppDelegate *appDelegate = (NexusReferenceAppDelegate *)[[UIApplication sharedApplication] delegate];
	
	PathListDB *path = (PathListDB *)[appDelegate.paths objectAtIndex:indexPath.row];
	[subpathListViewController setTitle: path.pathName];

	[[self navigationController] pushViewController: subpathListViewController animated: YES];

}

- (UITableViewCellAccessoryType)tableView:(UITableView *)tv accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
	return UITableViewCellAccessoryDisclosureIndicator;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	// Return YES for supported orientations
	return (interfaceOrientation == UIInterfaceOrientationPortrait);
}


- (void)didReceiveMemoryWarning {
	[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
	// Release anything that's not essential, such as cached data
}


- (void)dealloc {
	[tblView release];
	[subpathListViewController release];
	[super dealloc];
}


@end
 
You're not retaining it

You're pointing your path varable at a new object and it works, but you come back later, and it's gone? I don't think you're retaining the variable. It's getting autoreleased.

If you were using a property (set to retain) it would work fine. But since you're accessing the member directly, you need to retain it yourself.
 
hmm?

The only variable having a problem is pathName, primaryKey has no problem.

I am accessing the variables and declaring them just like the SQLiteBooks example.

This code isn't all in the same file but it demonstrates what I am doing.

Code:
NSString *pathName;
@property (copy, nonatomic) NSString *pathName;

pathName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 0)];

// performed in dealloc
[pathName release];

	NexusReferenceAppDelegate *appDelegate = (NexusReferenceAppDelegate *)[[UIApplication sharedApplication] delegate];

	PathListDB *tempPath = (PathListDB *)[appDelegate.paths objectAtIndex:indexPath.row];

// this line fails
	cell.text = tempPath.pathName;
 
fixed

Well the problem was a retain it seems...

- (void)setPathName:(NSString *)aString {

pathName = aString;
[pathName retain];

}

fixed it!

I guess my app is sufficiently different from SQLBooks that i need to have a retain there because it isn't on the books app.

Thanks
 
My guess was right. This line is NOT using the property, it's accessing the member directly, so you don't get the benefit of the (nonatomic,retain)

Code:
pathName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 0)];

The fix you made will work around the problem, but so will changing this line to:

Code:
self.pathName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 0)];
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.