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
 

xsmasher

macrumors regular
Jul 18, 2008
140
0
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.
 

dean1012

macrumors regular
Original poster
Jul 10, 2008
130
1
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;
 

dean1012

macrumors regular
Original poster
Jul 10, 2008
130
1
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
 

xsmasher

macrumors regular
Jul 18, 2008
140
0
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.