Variable not giving correct value

Discussion in 'iOS Programming' started by dean1012, Oct 8, 2008.

  1. dean1012 macrumors regular

    Joined:
    Jul 10, 2008
    #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
    
     
  2. xsmasher macrumors regular

    xsmasher

    Joined:
    Jul 18, 2008
    #2
    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.
     
  3. dean1012 thread starter macrumors regular

    Joined:
    Jul 10, 2008
    #3
    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;
     
  4. dean1012 thread starter macrumors regular

    Joined:
    Jul 10, 2008
    #4
    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
     
  5. xsmasher macrumors regular

    xsmasher

    Joined:
    Jul 18, 2008
    #5
    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)];
    
     

Share This Page