Why does my UINavigationController crash on release configuration? - iphone

I have a requirement to remove a previous view controller in a stack. I have successfully used this method in the past, without any problems:
NSMutableArray *vcs = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
BOOL removedFlag = YES;
while (removedFlag == YES)
{
removedFlag = NO;
for (UIViewController *vc in vcs)
{
if( ![vc isKindOfClass:[self class]] && ![vc isKindOfClass:[MenuVC class]] )
{
[vcs removeObject:vc];
removedFlag = YES;
break;
}
}
}
[self.navigationController setViewControllers:[NSArray arrayWithArray:vcs]];
Now I’m updating the app, and it mysteriously crashes on release configuration, not on debug. In addition, there are no warnings whatsoever in debug mode. I think something changed with the SDK. It never did this before.
I’ve made a sample project available:
http://dl.dropbox.com/u/7834263/RemoveTest.zip
This is an ARC-enabled project.

The first line should actually be:
NSMutableArray *vcs = [self.navigationController.viewControllers mutableCopy];
NSMutableArray is a subclass of NSArray. arrayWithArray is a method of the NSArray class -- it creates an immutable array. The mutableCopy method creates a mutable copy of the original array. You never really should have been able to call removeObject on your vcs array because it was never actually an NSMutableArray -- it was an NSArray.

A simple way to remove the previous viewController in the stack could be:
int vcIdx=[self.navigationController.viewControllers indexOfObject:self]-1;
NSMutableArray *vControllers = [[NSMutableArray alloc] initWithArray:self.navigationController.viewControllers];
[vControllers removeObjectAtIndex:vcIdx];
self.navigationController.viewControllers=vControllers;

Related

EXC_BAD_ACCESS and Zombies, Yet not really sure why it keeps coming up

I don't know what's going wrong here. The crash happens when switching back and forth between views.
Here's what instruments gives me:
Clicking into it references this code with the first action :
-(IBAction)pushnews; {
NewsViewController *news = [[[NewsViewController alloc]init]autorelease];
news.title =#"Page";
[self.navigationController pushViewController:news animated:YES]; }
I use autorelease sometimes but usually I just release it my self. Should I get rid of autorelease and add [news retain]
What am I doing wrong?
Edit based on answers:
Following EmptyStack's Advice: ViewWillDisappear Code looks like this:
- (void)viewWillDisappear:(BOOL)animated {
webView.delegate = nil; }
This seems to resolve issues (pending more testing)
In viewdidload I said: webView.delegate = self;, which may have been the issue!
My guess is that, there is a UIWebView in NewsViewController, and it is causing the crash. It is possible that, a delegate method of web view is called after the web view is released. If so, try to setwebView.delegate = nil; in NewsViewController's viewWillDisappear: method.
try this instead :
-(IBAction)viewcontroller;
{
NewsViewController *news = [[NewsViewController alloc]init];
news.title =#"Page";
[self.navigationController pushViewController:news animated:YES];
[news release];
}

Is this release call causing instruments to crash?

I have a class called CategoryViewController and its viewDidLoad method calls this method:
- (void)reset {
Category * c = [[Category alloc] initWithId:0 title:#"Categories"];
[self setCategory:c];
[c release]; // <--- line of code I am interested in
self.title = #"Categories";
[self fillCategory];
}
In most situations category here would be null, but sometimes reset needs to be called after category has been assigned. Without the line I marked in my code, the program builds and debugs just fine and I can pull up instruments and check my leaks. The only leak I can find is a category initialized from this function (because without the release, I get a leak when calling this function on a CategoryViewController that has already been initialized).
If I try to run this method as is WITH the release on c, Instruments, XCode, and the Simulator all begin to act strangely, crashing and freezing, giving me random SIGABRTs and SIGKILLs. I can build and debug with the line of code in there, but Instruments won't even start my app. Can anyone give me a hint as to what's going on here?
EDIT: More code
#implementation Category
#synthesize title, articleCount, seeAlso, categoryId, articles, subcategories;
- (id)initWithId:(NSInteger)cid title:(NSString*)t{
self.title = t;
self.categoryId = cid;
[self setArticles:[[NSMutableArray alloc] init]];
[self setSubcategories:[[NSMutableArray alloc] init]];
[self setSeeAlso:[[NSMutableArray alloc] init]];
self.articleCount = 0;
return self;
}
It's funny how these things seem to be resolved so easily after you take the time to post them online. After posting the Category init code I realized I wasn't properly releasing the allocations I made. My leaks as well as my crashes appear to be gone after proper memory management like so:
- (id)initWithId:(NSInteger)cid title:(NSString*)t{
self.title = t;
self.categoryId = cid;
NSMutableArray * m = [[NSMutableArray alloc] init];
[self setArticles:m];
[m release];
m = [[NSMutableArray alloc] init];
[self setSubcategories:m];
[m release];
m = [[NSMutableArray alloc] init];
[self setSeeAlso:m];
[m release];
self.articleCount = 0;
return self;
}
Assuming your property is declared like below, I don't see any problem with the line you highlighted, nor with the rest of your code:
#property (retain) Category *c;
Just shooting in the dark, but last time Xcode, Instruments and the Simulator did crazy stuff, I had an infinite recursion going on.
Are you sure your method fillCategory doesn't somehow call your viewDidLoad or reset method?
What happens when you set a breakpoint on the first line of your reset method, and follow your code step-by-step?

Initializing view controller referenced from application launch

My data is stored in a member variable (NSArray) of a view controller. The problem I'm running into is that my data is loaded from a database on application launch, but the NSArray isn't initialized until later, so the addObject calls silently fail.
I've tried putting breakpoints on the init, initWithNibName, viewWillAppear, and viewDidLoad methods of my view controller (SafeTableViewController), but none of them catch before the addObject call. I assume that the actual view controller is initialized, because when I watch it in the debugger it has a nonzero address, but the NSArray has the address 0x0 when addObject is called.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
databaseName = #"DubbleDatabase.sql";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
[self checkAndCreateDatabase];
[self readSafeItemsFromDatabase ];
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
return YES;
}
- (void) readSafeItemsFromDatabase {
// some code skipped here, but basically: open sqlite3 database, iterate through rows
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// read database, get data fields out
SafeItem *safeItem = [[SafeItem alloc] initWithName:aName price:aPrice category:aCategory];
[safeTableViewController addItemToSafe: safeItem]; // PROBLEM HERE
[safeItem release];
}
}
sqlite3_close(database);
}
In SafeTableViewController.m:
- (void) addItemToSafe : (SafeItem*) newSafeItem {
[self.safeItems addObject: newSafeItem];
}
// I put a breakpoint on this, but it does not hit. i.e. safeItems is not initialized when addObject is called on it.
-(id) init {
if(self = [super initWithNibName:#"SafeTableViewController" bundle:nil]){
self.safeItems = [[NSMutableArray alloc] init];
}
return self;
}
EDIT: Thought of a way to fix this problem. Still curious though: when is init and/or initWithNibName called? Here's the proposed solution:
- (void) addItemToSafe : (SafeItem*) newSafeItem {
if(self.safeItems == nil){
self.safeItems = [[NSMutableArray alloc] init];
}
[self.safeItems addObject: newSafeItem];
}
How do you setup your instance of SafeTableViewController? By code? By nib?
-(id) init is not the designated initializer.
You probably want to use
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle {
if(self = [super initWithNibName:nibName bundle:nibBundle]){
self.safeItems = [[NSMutableArray alloc] init];
}
return self;
}
or initialize elsewhere, i.e. in viewDidLoad.
The problem is that you shouldn't be storing your data in a view controller. Create a model object (SafeItemManager for instance) to hold your data and point the view controller at that.

NSCoding Serialization of iPhone NavigationController stack

EDIT: I seem to have found something that's helped. I retained the ivar "stack" and now it seems to be working
I have been serializing several custom NSObject classes without issue. Now I'd like to serialize my NavigationController stack. Each viewController only needs a couple of properties saved in order to rebuild the navigation tree. I've implemented the NSCoding protocol in the viewControllers, and successfully coded them to NSData and saved to disk.
When I attempt to load the stack, the resulting array has the correct number of objects, but I keep getting EXC_BAD_ACCESS errors when I try to set the viewController array. Am I just going about this the wrong way?
//AppDelegate.m
-(void) loadDataFromDisk {
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *programDataPath = [libraryPath stringByAppendingPathComponent:#"programData.dat"];
NSData *programData = [[NSData alloc] initWithContentsOfFile:programDataPath];
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:programData];
//stack is a mutable array declared in header
//stack = [decoder decodeObjectForKey:#"stack"];
stack = [[decoder decodeObjectForKey:#"stack"]retain]; //retain fixes? Seems to work
[decoder release];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
NSLog(#"%#",self.navigationController.viewControllers);
if ([stack count] > 1) {
self.navigationController.viewControllers = stack;
[stack release]; //retained earlier
}
return YES;
}
I had to -retain the viewController stack after loading it from disk. Evidently if you don't immediately assign the data to a retained property it vanishes.

Tab bar Controller raising NSInternalInconsistencyException

In my SDK 3.0 core data based app, I have a tab bar controller managing 4 tabs. From time to time, apparently randomly, when I launch the app, it crashes with the following message:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Directly modifying a tab bar managed by a tab bar controller is not allowed.'
However, I am not modifying in my code any of the tabs except for the following. In practice, all of the navigation controllers or controllers in the tabs have been setup using IB, and in my code I have declared two of them as outlets, since I need to access them in my app delegate applicationDidFinishLaunching() method to setup their core data managedObjectContext as follows:
[self managedObjectContext];
[self managedObjectModel];
[self persistentStoreCoordinator];
[rootViewController retain];
rootViewController.managedObjectContext = self.managedObjectContext;
Is this not correct? If so, why?
The only reference to the problem I have seen on the web is here:
http://discussions.apple.com/thread.jspa?messageID=9716886
However, the problem still persists even after deleting and recreating the tab bar controller from scratch in IB.
Any clue?
Thanks in advance.
I've had this problem too. Do you have an outlet to the UITabBar itself (not the UITabBarController) in the nib? When I removed that, I stopped having problems.
Sorry this isn't a 100% reliable explanation, but this workaround cleared the problem up for me.
I've gotten this exception a few times, especially when changing things with localizations. Cleaning the targets and then rebuilding seems to work around the issue.
I quickly wrote the following class and showing/hiding tab views from UITabBarController worked like magic:
TabBarDesigner.h
#import <Foundation/Foundation.h>
#interface TabBarDesigner : NSObject
{
}
+(void) setTabBarController:(UITabBarController *)tabBarController
items:(NSArray *)tabBarItems
viewControllers:(NSArray *)viewControllers;
+(void) removeItemsInRange:(NSRange) range;
#end
TabBarDesigner.m
#import "TabBarDesigner.h"
static NSArray *_tabBarItems = NULL;
static NSArray *_viewControllers = NULL;
static UITabBarController *_tabBarController = NULL;
#implementation TabBarDesigner
+(void) setTabBarController:(UITabBarController *)tabBarController
items:(NSArray *)tabBarItems
viewControllers:(NSArray *)viewControllers
{
if (tabBarItems && viewControllers && tabBarController)
{
if ([tabBarItems count] == [viewControllers count])
{
[_tabBarItems release];
[_viewControllers release];
_tabBarItems = [tabBarItems copy];
_viewControllers = [viewControllers copy];
_tabBarController = tabBarController;
}
}
}
+(void) removeItemsInRange:(NSRange) range
{
if (_tabBarController)
{
if ( range.location < ([_tabBarItems count] - 1) )
{
if ( (range.length + range.location) < [_tabBarItems count] )
{
NSMutableArray *tabBarItems = [_tabBarItems mutableCopy];
[tabBarItems removeObjectsInRange:range];
NSMutableArray *viewControllers = [_viewControllers mutableCopy];
[viewControllers removeObjectsInRange:range];
[_tabBarController setViewControllers:viewControllers];
NSUInteger i;
for (i = 0; i< [viewControllers count]; i++)
{
UIViewController *vC = [viewControllers objectAtIndex:i];
vC.tabBarItem.image = [[tabBarItems objectAtIndex:i] image];
vC.tabBarItem.title = [[tabBarItems objectAtIndex:i] title];
vC.tabBarItem.tag = [[tabBarItems objectAtIndex:i] tag];
}
[tabBarItems release];
[viewControllers release];
}
}
}
}
#end
A sample of how to use this class:
In your MyAppDelegate.m
#import "TabBarDesigner.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[TabBarDesigner setTabBarController:_tabBarController
items:[_tabBarController.tabBar items]
viewControllers:[_tabBarController viewControllers]];
// remove the first 3 tabs
[TabBarDesigner removeItemsInRange:NSMakeRange(0,3)];
// show all tabs
[TabBarDesigner removeItemsInRange:NSMakeRange(0,0)];
// continue with your code
}
Cheers!