iPhone app crashes when attempting to access appDelegate member - iphone

I'm an Objective-C newbie and I'm studying iPhone programming.
In my appDelegate, in the -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method, I've a class member (#syntethized) called databasePath.
I set its value this way:
databasePath = [self copyDatabaseToDocuments];
I copied the entire copyDatabaseToDocuments method from a wonderful book by Alasdair Allan and made very little changes (the name of the db is the only thing I changed):
-(NSString *)copyDatabaseToDocuments{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath=[paths objectAtIndex:0];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"myDb.sqlite"];
//
if(![fileManager fileExistsAtPath:filePath]){
NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"myDb.sqlite"];
[fileManager copyItemAtPath:bundlePath toPath:filePath error:nil];
}
return filePath;
}
I NSLog the databasePath and I regularly get its value (it is a string path and it is not null) after the assignment.
Then, I have a method -(NSMutableArray*)readDatabase:(char*)querySQL I call from a ViewController through a delegate reference.
Anything works fine if -inside this last method- I assign again the value of databasePath.
But, if I don't assign it again AND I want to use its value (that I suppose it was set in the -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method) the app crashes.
Why?

Make sure that your #property for databasePath looks like this:
#property (nonatomic, retain) NSString *databasePath;
And then set it in this way:
self.databasePath = [self copyDatabaseToDocuments];
It is probably crashing because copyDatabaseToDocuments returns an autoreleased string, and unless you use the self. notation to set databasePath, that autoreleased string can go away at any time.

At a guess, since you don't show the relevant code, you are not retaining the value of databasePath. You assign it directly to the ivar in your code sample above, yet the method you show returns an autoreleased string.
I'll guess your property is defined as retain or copy. You should therefore set the value as
self.databasePath = [self copyDatabaseToDocuments];
This will then retain or copy the value for you. Synthesizing property accessors does you no good if you don't use them!

Related

Why redefine pListPath in didSelectRowAtIndexPath?

So I've read a ton of SO-questions about plists and how to save to them, and although I don't know, why no iPhone-Dev-Book I've seen so far covered this (they all used the tableView editing function), I managed to REALLY write to a plist by copying it to the documents folder like this:
pListPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
pListPath = [pListPath stringByAppendingPathComponent:#"piggyBanks.plist"];
NSLog(#"Path: %#", pListPath);
// if the file does not exist yet, create it, and copy the plist data into it, that can be found in the bundle
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:pListPath]) {
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:#"piggyBanks" ofType:#"plist"];
[fileManager copyItemAtPath:sourcePath toPath:pListPath error:nil];
}
// make the plist content available for usage
pListContent = [[NSMutableDictionary alloc] initWithContentsOfFile:pListPath];
NSLog(#"pListContent: %#", pListContent);
So far so good, but now, if I wanna change some plist value when a user taps on a tableViewCell (it's a custom one, if that's important), although pListPath, pListContent and others are properties, defined in .h and synthesized in .m, I have to redefine pListPath and pListContent inside didSelectRowAtIndexPath, to get the path to be known in that moment.
Could someone please tell me why? I mean, it's nice, that it works, but I'd like to know, why it has to be like that, or if I did a mistake somewhere else..
Thanks!
If plistPath is a string property of your class, you need to assign it as such:
if (!self.pListPath)
{
NSString *newPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
newPath = [newPath stringByAppendingPathComponent:#"piggyBanks.plist"];
self.pListPath = newPath;
}
Then afterwards, use self.pListPath instead of pListPath.
You are having to re-set it because at the moment, pListPath is being assigned to an autoreleased string which will have been removed by the time you need it again. Setting the property (assuming the property is retained or copied) will retain the string for you.
I googled for class variables in objective C, and found my way through various articles, till i found this blog/blogpost, which explains the self. and _underscore thing really well!
I now always declare my Ivars with an underscore, the properties without. Works out quite well. And to sum it up, why to ALWAYS use self.yourPropertiesName, you are calling a method (setter and getter)! And like any other method you are calling, you need to say, who is calling.
Hope this will help someone else too :)

How to call appdelegete method in different class in iphone

I have a database with 10 tables. As I need to access this database in different view controllers, I have to declare the two methods shown below in each of them. Is there a way I can avoid this by declaring these methods in the application delegate. If yes, how can I go about using these methods in different classes.
- (NSString *) getWritableDBPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
return [documentsDir stringByAppendingPathComponent:DATABASE_NAME];
}
-(void)createEditableCopyOfDatabaseIfNeeded
{
// Testing for existence
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:DATABASE_NAME];
NSLog(#"%#",writableDBPath);
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:DATABASE_NAME];
success = [fileManager copyItemAtPath:defaultDBPath
toPath:writableDBPath
error:&error];
if(!success)
{
NSAssert1(0,#"Failed to create writable database file with Message : '%#'.",
[error localizedDescription]);
}
}
in your view controller first of all create a delegate variable
YourAppDelegate *appDelegate=(YourAppDelegate *)[[UIApplication sharedApplication]delegate];
then u can call any methods that you have define in your delegate
like [appDelegate methodName];
This just screams to be implemented as a separate controller with class level methods. I would highly recommend creating a Database controller with a definition like so:
#interface DatabaseController: NSObject
+ (NSString *) getWritableDBPath ;
+ (void) createEditableCopyOfDatabaseIfNeeded ;
#end
Then in your code using it as so:
#import "DatabaseController.h"
NSString * somePath = [DatabaseController getWritableDBPath];
[DatabaseController createEditableCopyOfDatabaseIfNeeded];
Set them as public, so you can call them with [ ]
You just need to change the minus for +
+(void)createEditableCopyOfDatabaseIfNeeded;
You will need to define a protocol for this class and add a variable of that protocol to the member variable of this class as follows:
The classes where the object is created can either call this method using the object. The Best option is to use the app delegate class to implement these methods.
You can then assign the objects's delegate as the app delegate and call the methods.
#protocol mySqlDelegate ;
#interface mySqlClass {
id <mySqlDelegate> delegate;
}
#property (nonatomic, assign) id <mySqlDelegate> delegate;
#end
#protocol mySqlDelegate
- (void) delegateMethodsForThisClass;
#end
first create a common instance for appdelegate.
otherwise in constant.h file create a instance like
mAppDelegate=(YourAppDelegate*)[[UIApplication sharedApplication] ];
then just import constant.h and you may use mAppdelegate anywhere so using this you easily call

Objective-C Memory Issue

I'm having an issue with the memory management in my application. I have an NSDictionary instance variable that I'm setting equal to another NSDictionary that gets made in a method. This all works fine and my application behaves like I want it to, but I'm having trouble applying the proper memory management.
If I release the local dictionary it eventually causes a crash as the method is called repeatedly, because the data saved in the instance variable is also trashed. Here's the code:
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:#"Names" ofType:#"plist"];
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.dictAllValues = dictionary;
[dictionary release];
Create dictAllValues using
#property(retain) NSDictionary *dictAllValues;
Your method
-(void) myMethod
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:#"Names" ofType:#"plist"];
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.dictAllValues = dictionary;
[dictionary release];
}
and release in dealloc method
-(void) dealloc
{
[dictAllValues release];
[super dealloc];
}
How do you declare dictAllValues? Typically, it would be:
#property(retain) NSDictionary *dictAllValues;
If so, then the release in your code is correct and your problem lies elsewhere. Post the backtrace of the crash, use Build and Analyze and fix any issues, and try turning on Zombie detection.
From the apple memory management guide.
As a corollary of the fundamental rule, if you need to store a received object as a property in an instance variable, you must retain or copy it.
So, in this case putting [dictionary release]; in dealloc method instead (or any other method you might use for clean up) should work fine.
I assume your dictAllValues property uses simple assignment, let me know if that's not the case.

Can I write an array of UILocalNotifications to disk?

I'm trying to use the following code to persist the current list of local notifications. NSArray explicitly lists the kind of objects it will work with, which implies I can not use this with an array full of UILocalNotification objects. However, UILocalNotifications does implement NSCoding, which led me to believe there must be an easy way to serialize/deserialize this list of objects. Do I need to do the encoding and file persistence myself? Also, is there a way to get more information about why the write failed?
- (NSString*)getSavedNotifsPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingString:#"saved_notifs.plist"];
}
- (void)prepareToHide {
UIApplication* app = [UIApplication sharedApplication];
NSArray *existingNotifications = [app scheduledLocalNotifications];
if (! [existingNotifications writeToFile:[self getSavedNotifsPath] atomically:NO] ) {
// alert
[self showSomething:#"write failed"];
}
}
First, change the code
return [documentsDirectory stringByAppendingString:#"saved_notifs.plist"];
to
return [documentsDirectory stringByAppendingPathComponent:#"saved_notifs.plist"];
stringByAppendingPathComponent: will ensure a slash (/) is included, if needed, before the file name.
NSArray can only save property list objects, which UILocalNotification is not. Instead, try using NSKeyedArchiver. For example:
- (void)prepareToHide {
UIApplication* app = [UIApplication sharedApplication];
NSArray *existingNotifications = [app scheduledLocalNotifications];
NSString *path = [self getSavedNotifsPath];
BOOL success = [NSKeyedArchiver archiveRootObject:existingNotifications toFile:path];
if (! success ) {
// alert
[self showSomething:#"write failed"];
}
}
Use NSKeyedUnarchiver to retrieve the array from the saved file.
Note: I have not actually tested this so I'm not 100% sure it will work. But give it a try and see what happens.

How to release NSString

NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [path objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:#"DB"];
NSString *fileName = [newWordbookName stringByAppendingString:#".csv"];
NSString *fullPath = [databasePath stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] createFileAtPath:fullPath contents:nil attributes:nil];
[databasePath release];
//[fileName release]; Error!
//[fullPath release]; Error!
//NSLog(#"#1 :databasePath: %d",[databasePath retainCount]);
//NSLog(#"#1 :fileName: %d",[fileName retainCount]);
//NSLog(#"#1 :fullPath: %d",[fullPath retainCount]);
I'm using this code and want to release NSString* ..
so, I declare fileName, fullPath, and databasePath of NSString.
database is released but fileName, fullpath doesn't release. I don't know why it happens.
I know that NSArray is Autoreleased. But is documentsDirectory autoreleased?
(newWordbookName is nsstring type)
I hope that I look through a document about iPhone memory management.
By convention the only two cases when a method returns a retained object are constructors i.e. alloc, new etc. and object-copying methods (containing copy in their name).
In all other cases the object is expected to be autoreleased, unless explicitly stated otherwise in the documentation.
This is the complete memory management documentation:
Cocoa Memory Management
You should not be calling release on any of the objects in the above code.
The reason the NSArray is autorelease'd is the same reason all the other objects are autorelease'd: the methods that assigned them their values called autorelease on them before they returned. In general, you can assume methods return autorelease'd objects if they do not have the word "create" or "new" in them. That is the general Cocoa convention. (Although 3rd party code may be goofy and do things differently, so caveat programmer).
You only really need to worry about objects you alloc or copy yourself; in other words, pair every alloc or copy with a release or autorelease.