UINavigationController crashes due to a NSArray variable - cocoa-touch

I push a view controller into current navigation controller. It works fine, except when I am getting out of the current view controller, it crashes.
MyTableView *newPage = [[MyTableView alloc] initWithNibName:#"table2" bundle:nil];
[[self navigationController] pushViewController:newPage animated:YES];
//[newPage release];
I comment out the last line to prevent crash. I read another post about variables being over released. In the newPage, I only have one variable (arrCellText), and is initialized in the initWithNibName
NSArray *temp = [[NSArray alloc] initWithObjects:#"string1", #"string2", #"string3", nil];
[self setArrCellText: temp];
[temp release];
I put the release in the dealloc
[arrCellText release];
If I comment out setting and release of arrCellText, it works fine too.
I must not have complete understanding of memory management, and I would like to understand this better. TIA

Where does the crash happen exactly?
First you can release 'newPage' after pushing it onto the navigationController (because it's retained there).
You might try to access anything from newPage after coming back. 'newPage' was released in the mean time and thus has some garbage value (but not nil).

Related

call a method after the object is released?

i was reading this code, where setRegions is called after RootViewController is released : i find it a bit strange : does it mean RootViewController is still accessible, even if it was released and self.navigationController "owns" it ?
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create the navigation and view controllers
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
[aNavigationController release];
[rootViewController release];
[rootViewController setRegions:[Region knownRegions]];
// Configure and display the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
Thanks
This is bad code.
An object should retain another object for as long as it cares about it. And in this case that rule is broken. The rootViewController is released, and then as you note, a method is called on it. This can be dangerous.
In this case, it works. This is because rootViewController is passed to another object, which retains it. So when we release it, it still has a positive retain count and is not deallocated. So our reference to it still works, and methods called on it work fine.
But lets say some implementation changed and initWithRootViewController: now no longer retained it's argument for some reason (an assumption you can't really make all the time). Suddenly this all crashes because rootViewController gets deallocated.
To fix this funk, you just need to move [rootViewController release]; to after the last useful reference of that object in this function. Your code then becomes more robust and more correct.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create the navigation and view controllers
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
[rootViewController setRegions:[Region knownRegions]];
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
// Release temporary objects since we've now sent them to other other objects
// which may or may not retain them (we don't really care which here)
[aNavigationController release];
[rootViewController release];
// Configure and display the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
Last thing to note: release and dealloc are very different things. release does not necessarily destroy objects. It simply decrements the retain count by one. And if that retain count ever gets to zero, only then is the object is deallocated. So this code works because a release happens but without triggering a dealloc.
The above is very dangerous code. It might happen to work, but it's just getting lucky. You should never access a variable after you have released it. In fact, it is best practice to immediately set variables to nil after releasing them if they don't immediately go out of scope. Some people only do this in Release mode, and so create a macro like:
#ifdef DEBUG
#define RELEASE(x) [x release];
#else
#define RELEASE(x) [x release]; x = nil;
#endif
The reason for this is to help catch bugs in debug mode (by having a crash rather than just a silent nil pointer), while being a bit safer in release mode.
But in any case, you should never access a variable after you've released it.
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
(objectA created, retain count is 1, rootViewController points to it)
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
(objectB created, retain count is 1, aNavigationController points to it)
(objectA retain count is 2 now, both rootViewController and some property in self.aNavigationController point to it)
self.navigationController = aNavigationController;
(objectB retain count is 2 now, both aNavigationController and self.navigationController point to it; assuming self.navigationController is a retain property)
[aNavigationController release];
(objectB retain count is 1 now, however, both aNavigationController and self.navigationController point to it)
[rootViewController release];
(objectA retain count is 1 now, however, both rootViewController and some property in self.aNavigationController point to it)
[rootViewController setRegions:[Region knownRegions]];
(use rootViewController to access objectA)
(This is not good)
Following is my recommended way:
RootViewController *rootViewController = [[[RootViewController alloc] initWithStyle:UITableViewStylePlain] autorelease];
[rootViewController setRegions:[Region knownRegions]];
UINavigationController *aNavigationController = [[[UINavigationController alloc] initWithRootViewController:rootViewController] autorelease];
self.navigationController = aNavigationController;

What things should I look for that could be increasing my retain count in a UIViewController which doesn't dealloc

I have a UIViewController that is popped from the navigation stack on iPhone and removed by setRootViewController on the iPad.
In both cases the UIViewController fails to dealloc, which means that something is hanging on to it; this is confirmed by logging [self retainCount] which is two, right before the pop or the setRootViewController (for some reason it's four in ViewWillDisappear).
The UIView has audio (using Flite Text to Speech - which uses AVFoundation.framework) and animation.
What things should I look for that could increasing my retain count in the view controller and stopping the view controller from being politely dealloced as it should be.
Here's how the view is pushed onto the view stack or set as the RootViewController;
-(IBAction)pushShare:(id)sender{
ShareViewController *shareViewController = [[ShareViewController alloc] initWithNibName:#"ShareViewController" bundle:nil];
NSLog(#"1. SVC Retain count %d", [shareViewController retainCount]);
[shareViewController setParentIsIpadMake:YES];
NSLog(#"2. SVC Retain count %d", [shareViewController retainCount]);
[shareViewController setStory:story];
NSLog(#"3. SVC Retain count %d", [shareViewController retainCount]);
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
StoryBotAppDelegate *appDelegate = (StoryBotAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.window setRootViewController:shareViewController];
NSLog(#"4. SVC Retain count %d", [shareViewController retainCount]);
} else{
[self.navigationController pushViewController:shareViewController animated:YES];
NSLog(#"4. SVC Retain count %d", [shareViewController retainCount]);
}
NSLog(#"releasing Svc...");
[shareViewController release];
NSLog(#"5. SVC Retain count %d", [shareViewController retainCount]);
}
First, try running Xcode's static analyser, it may find what is wrong.
You can also try overriding retain method with calling [super retain] and logging. Moreover you can put a breakpoint there and look at the stack when Xcode stops on it.
Or you can run ARC conversion tool and see what happens.
Retain count method is next to useless. The system libraries will be retaining your VC and releasing it. Which is why sometimes it will be a strangely high number. There are lots of topics on this.
Don't be concerned about the view animation delegate having the VC. The view and the VC are intimately tied together and released together, and the view doesn't retain its animation delegate - it just assigns it as a reference.
If u are testing in the simulator, force a memory warning and see if it is dealloc'ed. Dealloc'ed isn't always called immediately when you pop the VC.
Run instruments leaks. Does it say the VC is leaking memory? Run instruments with Zombies to find out whe it is leaking.
I read this question about NSTimer stopping the dealloc of a UIView. Turns out I had a NSTimer firing my animations which was stopping dealloc from happening nicely. I fixed it by adding a method called killTimer:
-(void)killTimer{
[animationTimer invalidate];
animationTimer = nil;
}
which I called on closing the view.
Thanks heaps for the answers to this question though - they were super helpful. I didn't actually know about the static analyzer until asking - or about how useless retain counts are; so +1 to you both.

NSFetchedResultsController keep reference to deallocated delegate controller in storyboard, causing crash

I have a simple UIViewTable, with a drill-down detail realized with a UINavigationController push segue in a storyboard.
It happen from time to time, that the table view controller seems to gets deallocated, while I am in the detail view, therefore I get the famous:
[MyViewController controllerWillChangeContent:]: message sent to deallocated instance
I explain better, I have an NSOperation queue which load my data asynchronously, and fill the table as soon as it just finished. The data are correctly retrieved and the table filled.
For the detail view, I am clicking on a cell and passing the NSManagedObjectID to the destination controller in prepareForSegue method. Very randomly when I made a change to the detail view the fetched controller loose its delegate, or as it seems, the delegate itself which is the controller gets deallocated. Causing a crash.
The fetched results controller is declared as a property:
#property(nonatomic,strong) NSFetchedResultsController *fetchedResultsController;
Then this is how everything is working starting from viewDidLoad.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadDataAsynchronously];
}
-(void)loadDataAsynchronously {
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(loadData)
object:nil];
[queue addOperation:operation];
}
-(void)loadData {
NSFetchRequest *findAllEntities = [[NSFetchRequest alloc] init];
[findAllEntities setEntity:ENTITY_DESC];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:YES];
[findAllEntities setSortDescriptors:[NSArray arrayWithObject:sort]];
[findAllEntities setFetchBatchSize:20];
[NSFetchedResultsController deleteCacheWithName:#"MyCache"];
if(self.fetchedResultsController==nil) {
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:findAllPlants
managedObjectContext:MOC
sectionNameKeyPath:nil
cacheName:#"MyCache"];
self.fetchedResultsController.delegate=self;
}
NSError *error=nil;
if (![FRC performFetch:&error]) {
exit(EXIT_FAILURE);
}
[self.dataTableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
this code works, and most of the time also works within the detail view which is called as a segue like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
IP2SegueIdentifier segueIdentifier = [IP2Factory segueSolver:[segue identifier]];
MyDestinationViewController *dvc = [segue destinationViewController];
NSIndexPath *indexPath = [TV indexPathForSelectedRow];
dvc.entityID=[[self.fetchedResultsController objectAtIndexPath: indexPath] objectID];
}
and the destination controller correctly get the entity id and reconstruct the object by asking the context.
Then, when I am in the detail view controller, I can make change to the entity, and when I go back to the navigation hierarchy I do a context save.
It is at this point that the application crashes, just right at context save. Not so often, but from time to time.
Because the fetched results controller recognize the change and submitted to its delegate which is already deallocated.
I have few doubts at this point, I am using iOS 5 and ARC so the compiler is supposed to have (almost) full control over release and dealloc methods. And I am also using a storyboard with a simple navigation hierarchy, which should guarantee that the whole previous view controllers chain gets retained.
I also run the profiler for memory leak/zombies analysis, but wasn't able to spot anything wrong, on the contrary I was happy the all the objects management were fine.
I have not many guess at this point, so please feel free to point out something I could have forgotten to check, or something you see wrong in my code.
thanks
First, a note about ARC. While ARC provides auto-zeroing weak pointers, it does not make assign pointers auto-zeroing. NSFetchResultsController uses an assign property for its delegate (see NSFetchedResultsController.h):
#property(nonatomic, assign) id< NSFetchedResultsControllerDelegate > delegate;
It is still your responsibility to clear yourself as the delegate before you deallocate. You typically do this in dealloc:
- (void)dealloc {
_fetchedResultsController.delegate = nil;
}
You may also want to get rid of your fetchedResultsController in viewWillDisappear: (including removing yourself as the delegate). Typically you do not want fetch requests to stay around when you are offscreen. (If you do, you probably should manage the fetch in a model object rather than in a view controller, since a view controller can go away anytime its view is offscreen.)
Your loadData is strange in that it creates a findAllEntities, but actually uses findAllPlants. Was this a typo or a bug? If there is a separate findAllPlants fetch request in an ivar, this could also be a cause of your problems.

Analyzer warning about incorrect decrement of reference count

I just installed Xcode 4 and opened an earlier version of my app. The analyzer is reporting for this line:
[self.myViewControllerObject release];
incorrect decrement of the reference count of an object that is not owned at this point by the caller
I didn't enable ARC for my project.
When I analyze v2.0 of my app in Xcode 3.2.5, it doesn't show any potential error.
Header:
#class MyViewController;
MyViewController *myViewControllerObject;
#property ( nonatomic , retain ) MyViewController *myViewControllerObject;
Implementation:
#import "MyViewController.h"
#synthesize myViewControllerObject;
When a button is clicked I have:
TRY 1:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
[self.myViewControllerObject release];
TRY 2:
MyViewController *temp = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
self.myViewControllerObject = temp;
[temp release];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
[self.myViewControllerObject release];
TRY 3:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
In the dealloc method, I release it:
[self.myViewControllerObject release];
The warning comes from you calling release on a property through the accessor: when you do [self.myViewControllerObject release] you are actually calling the accessor method myViewControllerObject and then release on the return value. Since the name of the method does not begin with new, copy, or mutableCopy, you do not own the object it returns, hence you are not “allowed” to release it.
The solution is to never call release on the return value of that accessor, so basically your try #2 was fine:
MyViewController *temp = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
self.myViewControllerObject = temp;
[self.navigationController pushViewController:temp animated:YES];
[temp release];
But in dealloc do not use the accessor, rather:
[myViewControllerObject release];
If you need to release myViewController other than in dealloc, assign nil through the setter:
self.myViewControllerObject = nil;
Edit: For more on the subject, see Apple's Advanced Memory Management Guide.
The navigation controller manages the lifecycle of view controllers you add to it. In this way, when you push the view controller, you are passing 'ownership'. However, there's no reason you can't also maintain a reference to the view controller. Try this order:
self.myViewControllerObject = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.myViewControllerObject release];
[self.navigationController pushViewController:self.myViewControllerObject animated:YES];
Alloc'd objects start with a retain count of one. Assigning a value to your 'retain' property increments the retain count by one. You should only keep one retain for the object, so you correctly release. But since you're starting at two, it's fine to release it before you pass it to the navigation controller. The compiler warning you see is just that - a warning, not always evidence you are doing something strictly wrong. It shouldn't matter if you pass it then release, but the order above should avoid it.
As I used the XCode 4 Beta version, the problem occurred. But when I tried it in the XCode 4 version, the Analyzer warning didn't occur to me. I Thank you to all whoever participated to help me.Thank you for your time.

Memory Management

How is method removeFromSuperView: really works?
I got a problem of memory bad access when I want to reinit the view
- (id)init {
if (!(self = [super init]))
return nil;
_mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
NSLog(#"retainCount :%d", [_mainView retainCount]);
UIButton *reInitButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f,0.0f,90.0f,35.0f)];
[reInitButton addTarget:self action:#selector(buttonDidTapped:) forControlEvents:UIControlEventTouchUpInside];
[[self view] addSubView:_mainView];
NSLog(#"retainCount :%d", [_mainView retainCount]);
[_mainView release];
NSLog(#"retainCount :%d", [_mainView retainCount]);
return self;
}
- (void)buttonDidTapped:(id)sender {
[_mainView removeFromSuperView]; //crash during second times press the button
NSLog(#"retainCount :%d", [_mainView retainCount]);
_mainView = [[UIView alloc] initWithFrame[[UIScreen mainScreen] bounds]];
[[self view] addSubView:_mainView];
NSLog(#"retainCount :%d", [_mainView retainCount]);
[_mainView release];
NSLog(#"retainCount :%d", [_mainView retainCount]);
}
I have NSLog every times there are any retain or alloc or release keyword. And the result is very weird.
//init
retainCount : 1
retainCount : 2
retainCount : 1
//1st time pressed button
retainCount : 1 //remove super view didn't decrease
retainCount : 2
retainCount : 1
//2nd time pressed button
retainCount : 0 //crash. Memory bad access
The weird thing is why it didn't crash on 1st time pressed??
I think your problem is here:
[_mainView release];
You've dropped your reference to _mainView, and yet, by my reading, that's a member variable that you'll keep around and continue to invoke methods on. That's not valid. Once you've called -release, you've essentially told the system you're not going to use that object again, and you can't do anything useful with a stale pointer to that object, like you do when you call -removeFromSuperView on it later.
If you want to continue to keep _mainView around and call code on it, you need to keep a reference. Perhaps you should move the release to your object's -dealloc method. Alternatively you could -release it in the button method and re-create a new view the next time you need to.
As a helpful tip, a lot of programmers like to reset objects to NULL (or nil in objC-speak) after releasing them, as a reminder that you can't use that object again. If you -release something, you'd better mean it.
Lastly, I suggest you Google the term "reference counting" and read up on it; it's a more generic idiom than the specifics of NSObject, and it is likely to be useful to think of the basics and how you might implement this in another language, like say, C. This will help you reason better about reference counted objects.
NEVER USE RETAINCOUNT. Sorry for putting that in caps, but I can't figure out for the life of me why people still use it. It's a faulty reference for memory management. Use instruments or similar instead.
You shouldn't be accessing _mainView at that point. This may be hard to explain, so bear with me. We're going to count, but not absolute retain count, just your code's claims on the object.
You allocate memory for an object and point at it with _mainView:
_mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
You have 1 claim of ownership to that object. When you add it as the subview of another view, that view likewise makes a claim of ownership, but that's not yours, it's the view's. The fact that it makes the object in _mainView stick around is an accident, and you shouldn't rely on it. Then you release the object:
[_mainView release];
You have relinquished your ownership claim -- you now have 0 claims, and you should no longer try to access this object. You don't own it. Again, the fact that it still exists because another view is using it, and the fact that you still have a pointer to it, are accidents*, and you should not rely on them.
When it comes time to handle your button press, then, you are accessing an object over which you have no ownership:
[_mainView removeFromSuperView];
and this causes a crash, which may not be expected, but it is not unreasonable. By letting your claims of ownership go to 0, you told the system "I don't need this object anymore. I'm not going to access it after this point. If it disappears, I will not be affected." In fact, though, you do need it to stay around, and you do need to access it.
What you should do, then, is move the line:
[_mainView release];
to inside the button action, right after the call to removeFromSuperview.
*The second of which could be avoided by setting _mainView = nil; after you release it, in this case, but that won't solve the greater problem.