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
NexusReferenceAppDelegate .h & .m
SubpathPathViewController .h & .m
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