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.
Related
this is my code:
array = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//make a file name to write the data to using the
//documents directory:
NSString *fullFileName = [NSString stringWithFormat:#"%#/file", documentsDirectory];
array = [NSKeyedUnarchiver unarchiveObjectWithFile:fullFileName];
What's Wrong in my code?
This is the error
[__NSArrayM count]: message sent to deallocated instance 0xd5864e0
The statement
array = [NSKeyedUnarchiver unarchiveObjectWithFile:fullFileName];
will give you back an autorelease object that will be deallocated at the first appropriate moment. So, when you later access its count method, the object is not there anymore. This is what is causing the crash.
A way to fix this is properly managing your array so that it stays there as long as you need it. If you are using ARC, this could mean managing the object through a strong property; if you are not using ARC, this involves using retain. You do not specify how array is declared so I cannot be more precise.
Since you say that the property is declared as:
#property (nonatomic,retain) NSMutableArray *array
simply doing:
self.array = [NSKeyedUnarchiver unarchiveObjectWithFile:fullFileName];
should fix the problem.
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 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.
In my app, I have a bunch of different image packs to download. The images are downloaded from my website one by one. The packs contain anywhere from 100-1500 images and each image is about 100KB-200KB.
When downloading the images, I have a method that selects the images to download, then I have a second method that does the actual downloading based on parameters sent from the main method. Here is the code for the download method:
-(BOOL) downloadImageName:(NSString *)imageName ImageGroup:(NSString *)imageGroup AndRow:(int)row {
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSURL *downloadURL = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.website.com/%#_0001.jpg",imageName]];
NSData *data = [NSData dataWithContentsOfURL:downloadURL];
NSString *savePath = [NSString stringWithFormat:#"%#/%#^%#_%#.jpg",docsPath,currentServerCycle,imageGroup,imageName];
BOOL downloaded = [data writeToFile:savePath atomically:YES];
if (downloaded)
return YES;
}
else {
return NO;
}
}
The problem I am having is this:
When I run this with performance tools looking at allocations I'm seeing that the app is keeping mallocs (NSConcreteData) each time an image is downloaded and only releasing them when the main method (the one calling this download method) completes. Thats fine for the smaller image packs, but the larger ones are obviously crashing after my total allocations hit something like 300+MB (the normal amount of allocations in my app is about 3mb).
They arent leaking, because once the image pack is downloaded and the method ends, all the mallocs are gone.
I have tried manually allocing and releasing the NSData *data but it has no effect.
Perhaps the autorelease pool the objects are in is not being drained until the main method returns. You might try adding NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; at the beginning of your download method, then [pool drain]; right before you return from the download method. You need to make sure you release the pool before you return no matter where you return otherwise you'll leak the pool.
Also, just as a point of standard Objective-C style, your method should be named:
(BOOL)downloadImageName:(NSString *)imageName imageGroup:(NSString *)imageGroup andRow:(int)row
with lowercase letters to start the "imageGroup:" and "andRow:" argument names.
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.