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.
Related
I used the following code to write values to dictionary, but when add new one to the dictionary it is not updating, it just displays the plist with only recently added value and it is crashing too.
nameString=nameTxt.text;
NSFileManager *mngr=[NSFileManager defaultManager];
NSArray *docDir=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath=[docDir objectAtIndex:0];
NSString *filePath=[docPath stringByAppendingPathComponent:#"score.plist"];
NSString *bundlePath=[[NSBundle mainBundle] pathForResource:#"score" ofType:#"plist"];
if ([mngr fileExistsAtPath:filePath]) {
NSLog(#"File exists");
}
else {
NSLog(#"NO file exists");
[[NSFileManager defaultManager] copyItemAtPath:bundlePath toPath:filePath error:NULL];
}
dict=[[NSMutableDictionary alloc]init];
dict=[NSMutableDictionary dictionaryWithContentsOfFile:filePath];
NSLog(#"dict is %#",dict);
[dict setObject:nameString forKey:#"100"];
[dict writeToFile:filePath atomically:YES];
[dict release];
I get crash when I used the last line "[dict release]"
I have a score.plist file in my bundle.
The crash is due to this line,
dict=[[NSMutableDictionary alloc]init];
dict=[NSMutableDictionary dictionaryWithContentsOfFile:filePath];
first line you are allocting memory and then you are overwriting the dict param to link to static dictionary which is not owned by you. So the old one is leaked and when you are releasing it tries to release the static one.
Instead of that use,
dict=[NSMutableDictionary dictionaryWithContentsOfFile:filePath];
and do NOT use release statement. Since you dont own it, you dont have to release it.
Check this
This is a simple memory problem.Along with solving the problem you have to understand the problem.
The dict is a NSMutableDictionary that you declared globally. And so that you can alloc it for using this so that you won't lose the scope of the dictionary.
So in the beginning say 'ViewDidLoad:' , you can alloc and init this as
dict=[[NSMutableDictionary alloc]init];
or in the present condition you can use like
dict=[[NSMutableDictionary alloc]initWithContentsOfFile: filePath];
So that you can alloc the dictionary with the score.plist file and everything will work fine.
What happended in your case is you alloced the dict. But in the next line you replace the alloced object of dict with autoreleaed object by the statement
dict=[NSMutableDictionary dictionaryWithContentsOfFile:filePath];
As the class methods always returns autoreleased objects, when you try to release the object which is autoreleased, it crashes. :-)
Hope you got the idea.
Now the solution is you can change the line
dict=[[NSMutableDictionary alloc]init];
To
dict=[[NSMutableDictionary alloc]initWithContentsOfFile: filePath];
And remove the line
dict=[NSMutableDictionary dictionaryWithContentsOfFile:filePath];
Everything will work. Happy Coding. :-)
I want to be able to refresh a view from another class, but nothing I have tried is working. My application is a tabbed application with several tabs set up. I have a method called refresh in my ViewController1 class that looks like this
-(void)TestMe{
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"widgjson" ofType:#"json"];
NSData *myData = [NSData dataWithContentsOfFile:filePath];
NSString *responseString = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString *docFile = [docDir stringByAppendingPathComponent: #"json.txt"];
[responseString writeToFile:docFile atomically:NO encoding:NSUTF8StringEncoding error:Nil];
[self loadView];
[self viewDidLoad];
}
This works fine. When the application first loads up, it loads a different json, then when I click this button it loads a new JSON and then updates the view. This is just temporary to test out refreshing. However if I try to call this method from another class, or from the AppDelegates ApplicationDidEnterForeground methods, nothing happens. I tried calling it like this
-(void)TestMe{
ViewController1 *vc = [[ViewController1 alloc] init];
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"widgjson" ofType:#"json"];
NSData *myData = [NSData dataWithContentsOfFile:filePath];
NSString *responseString = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString *docFile = [docDir stringByAppendingPathComponent: #"json.txt"];
[responseString writeToFile:docFile atomically:NO encoding:NSUTF8StringEncoding error:Nil];
[vc loadView];
[vc viewDidLoad];
}
So why does this method not work from any other classes. All I want to is to be able to refresh a view from another class or when the application loads up, but just can't seem to find anything that works.
Would be very grateful if someone could point me in right direction!
EDIT: UPDATING TO CLARIFY
Okay, what I have done for the time being which I don't like is put this
exit(0);
Inside AppDelegates ApplicationDidEnterBackground. This works to an extent, but is not an ideal solution.
What I would like to have happen is that when application is opened again, the AppDelegate gets run again which sets up the tabs. The JSON pulled from the server can affect the order of the tabs which is why I need the AppDelegates ApplicationDidFinishLaunching method to reload itself again.
Also I would like to know can I do this from another class in the application. The first time my application is loaded it asks for the users phone number, which is sent to the server which then generates an PIN. After this is done, then I want the AppDelegate method to load up and begin setting up the tabs and the order etc.
To prevent your application running in the background (and so to force reloading when you exit and re-enter) you can set the UIApplicationExitsOnSuspend key to YES in your info.plist.
However, this is probably a bit drastic.
Your current code for refreshing from the application delegate is a non-starter because you are creating a new instance of the view controller rather than talking to one that is on the screen.
For this reason it is best not to involve your application delegate at all in the process.
Instead, your view controller can register itself as an observer for the UIApplicationDidBecomeActive notification, and do whatever it needs to itself. You would typically register for this notification on viewDidLoad, and unregister on viewDidUnload (since, by definition, if you have unloaded the view, you don't need to refresh it when the app comes back up!).
You would register as follows (using blocks here as they are the future):
Declare an ivar of type id to hold the observer object, let's call it becomeActiveObserver in this case. So, wherever you are declaring your ivars, add id becomeActiveObserver. Then, in viewDidLoad:
becomeActiveObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidBecomeActive
object:nil
queue: nil
usingBlock:^(NSNotification *note){
//put your reloading code in here
[self TestMe];
}];
And remove like so in viewDidUnload:
[[NSNotificationCenter defaultCenter] removeObserver:becomeActiveObserver;
You can follow a similar pattern to pick up notifications (your own, or system ones) from anywhere else in your app. This is a better design than passing everything through the app delegate as it involves much looser coupling.
I guess clickMe is an action to a button.if you want to refresh the viewcontroller at startup then add a function in viewcontroller similar to clickme or any function that refreshes the view and call it from applicationWillEnterForeground method or you may try this :-
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSLog(#"app will enter foreground");
[viewController clickMe:NULL];
}
and you may change the file in the applicationWillEnterForeground method of appDelegate if your application requires.
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!
i've been reading for hours, searched apple's doc, stackoverflow, can't understand what i'm doing wrong....
when i use this data from a XML plist on my UITableViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"arrayofstrings"
ofType:#"plist"];
NSData *myData = [NSData dataWithContentsOfFile:path];
NSString *error;
NSPropertyListFormat format;
myArray = [NSPropertyListSerialization propertyListFromData:myData
mutabilityOption:NSPropertyListImmutable
format:&format
errorDescription:&error];
}
my tableview shows the first visible rows just fine but crashes when trying to scroll.
it doesn't happen when instead of the XML data i use something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
myArray = [[NSArray alloc] initWithObjects:#"thing1", #"thing2", #"thing3", #"thing4", #"thing5",#"thing6", #"thing7", #"thing8", #"thing9", #"thing10",
#"thing11",#"thing12", #"thing13", #"thing14", nil];
}
this way the tableview scrolls just fine. what's my problem?! Is the plist conversion to array supposed to be in any other way?
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"arrayofstrings"
ofType:#"plist"];
NSData *myData = [NSData dataWithContentsOfFile:path];
NSString *error;
NSPropertyListFormat format;
myArray = [[NSPropertyListSerialization propertyListFromData:myData
mutabilityOption:NSPropertyListImmutable
format:&format
errorDescription:&error] retain];
}
The return value from propertyListFromData:mutabilityOption:format:errorDescription is autoreleased. Make sure you call retain so it doesn't get released out from under you at the end of the current run loop.
The second method works because creating the NSArray with alloc/init leaves the array with a retain count of 1.
The problem is that in the first case your call to [NSPropertyListSerialization propertyListFromData: returns an NSArray with no retain count on it (note the method doesn't have alloc, new, or copy in the name) - and then you don't retain this NSArray. Hence, the array is getting deallocated shortly after, and your code crashes trying to access garbage memory.
In the second case, you are making an NSArray using alloc - this returns an NSArray with a retain count of 1, which means it isn't deallocated (until a release gets called at some point).
To fix this, in your first case you want to assign the array as follows:
self.myArray = ...
The self. is the crucial part here (assuming you have declared the myArray property as retain).
There's plenty of resources and blog posts available concerning memory management.
I seem to have a fundamental gap in my memory management understanding. The code below is located within a singleton that gets called multiple times within my app to parse data that is downloaded from the web. For each article I download, I allocate a mutable string, then do tons of parsing, then write the file to the file system for later display in a UIWebView.
But every time I enter this method, I allocate a new "articleString". And I never release this string. I think this is a leak, but if I add a release at the bottom of this method (after the file is written), my app crashes the next time this method is called. I don't understand why it crashes, since another NSMutableString is allocated next time it is called.
UPDATE: I do release articleString in the dealloc method. But it still seems that I should release at the end of this method, since I alloc every time I enter.
UPDATE: articleString is defined as follows in the header:
#property (nonatomic, retain) NSMutableString *articleString;
the parseArticle method below is a placeholder for a series of methods that manipulate articleString.
self.articleString = [[NSMutableString alloc] initWithData:articleData encoding:NSUTF8StringEncoding];
//Parse the article for display
[self parseArticle];
//Write the article string to a file for later display
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"article.html"];
NSLog(#"%#", articleString);
[articleString writeToFile:path atomically:YES];
I like to let properties handle this for me. If the articleString property is set to retain then this is simple.
self.articleString = [[[NSMutableString alloc] initWithData:articleData encoding:NSUTF8StringEncoding] autorelease];
[self doStuff];
Then
- (void)dealloc {
self.articleString = nil;
[super dealloc]
}
article string will get released and properly retain when you set a new one. And it will be cleaned up on dealloc.