Pushing UITableViewController onto [self navigationController] causes an EXC_BAD_ACCESS - iphone

In the current view that I am in, a button touch-up-inside event leads to the following action:
(Note that while in the Debugger, I've verified that both [self navigationController] and the instantiated historyViewController do indeed exist.
I am unable to determine why this bad access is happening. I can pop/push this view/other views from the navigation controller. Any ideas on how to go about investigating why this view in particular is having problems when getting pushed onto the nav controller?
-(IBAction) viewOrEditHistory: (id) sender {
HistoryViewController *historyViewController = [[HistoryViewController alloc] initWithStyle:UITableViewStyleGrouped];
historyViewController.title = #"View or Edit by date";
historyViewController.sameExSessions = [[NSMutableArray alloc] init];
historyViewController.exercise = [[Exercise alloc] initWithName:self.title muscleGroup:muscleGroupLabel.text];
/*** EXC_BAD_ACCESS happens after following line is executed ***/
[[self navigationController] pushViewController:historyViewController animated:YES];
}
Here is my HistoryViewController.h
#import
#interface HistoryViewController : UITableViewController {
NSMutableArray *sameExSessions;
Exercise *exercise;
}
#property (nonatomic, retain) NSMutableArray *sameExSessions;
#property (nonatomic, retain) Exercise *exercise;
-(NSMutableArray *) SameExerciseSessionList;
-(NSString *) getDocPath;
-(NSInteger) tableView:(UITableView *) tableView numberOfRowsInSection: (NSInteger)section;
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath;
#end

Please also get your memory management straight or you will run into a lot more problems. Every alloc should be followed by either a release or an autorelease.

Related

Where/If to release properties in a UITableViewCell

EDIT: Ok, so i figured out how to remedy my original problem, but i'm not sure if this is the best way.
My new question is, say I have a subclass of UITableViewCell with the following property declaration in the header:
#property (nonatomic, retain) IBOutlet UILabel *levelLabel;
This is connected in IB. Is it ok to not release this in dealloc, and not release it at all? This is the only way I can figure out to get it to work, without giving me an exc_bad_access error. Before, it called dealloc when the tableviewcell went off the screen but then it still needed it. where do i release stuff, or does it take care of that for me?
Original Title: Memory leak in UITableView and exc_bad_access
Ok, I am confused. I was following along with this tutorial online making custom UITableViewCells. I made one, and i did everything like the tutorial told me. My UITableViewCell subclass contains 3 UILabels and 3 UIButtons, and has all of them defined as properties and connected in IB. I need them to be available to the class because i need to know when the buttons are pressed and be able to change the text. When I run the app, i start scrolling and after a few seconds it crashes, with exc_bad_access in main (no output in the console). But when I run the app in instruments with NSZombieEnabled, it does not crash at all, and runs just fine. However, since instruments shows you the allocations, i can see them going up very quickly, especially as I scroll. I dont know if this is all allocations, or if these are being released, but still it seems too fast.
Here is PointCoordinatesCell.h (my custom cell):
#import <UIKit/UIKit.h>
#interface PointCoordinatesCell : UITableViewCell
#property (nonatomic, retain) IBOutlet UILabel *levelLabelLabel;
#property (nonatomic, retain) IBOutlet UILabel *levelLabel;
#property (nonatomic, retain) IBOutlet UILabel *levelDescriptionLabel;
#property (nonatomic, retain) IBOutlet UIButton *beginningButton;
#property (nonatomic, retain) IBOutlet UIButton *developingButton;
#property (nonatomic, retain) IBOutlet UIButton *secureButton;
#end
PointCoordinatesCell.m:
#import "PointCoordinatesCell.h"
#implementation PointCoordinatesCell
#synthesize levelLabel, levelLabelLabel, levelDescriptionLabel, beginningButton, developingButton, secureButton;
- (void)dealloc{
[super dealloc];
[levelLabel release];
[levelLabelLabel release];
[levelDescriptionLabel release];
[beginningButton release];
[developingButton release];
[secureButton release];
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
RootViewController.h has nothing in it other than a class declaration and standard imports. No variables or methods defined. It subclasses UITableViewController.
RootViewController.m:
#import "RootViewController.h"
#import "StatesAppDelegate.h"
#import "PointCoordinatesCell.h"
#implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}
#pragma mark Table view methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 50;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
return 293;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"PointCoordinatesCell";
PointCoordinatesCell *cell = (PointCoordinatesCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"PointCoordinatesCell" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (PointCoordinatesCell *) currentObject;
break;
}
}
}
//cell.capitalLabel.text = [capitals objectAtIndex:indexPath.row];
//cell.stateLabel.text = [states objectAtIndex:indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
// AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:#"AnotherView" bundle:nil];
// [self.navigationController pushViewController:anotherViewController];
// [anotherViewController release];
}
- (void)dealloc {
[super dealloc];
}
#end
It seems you are doing some complicated casting in your cellForRowAtIndexPath method. I do not think this is necessary. It does not seem logical to me to check for an object of class UITableViewCell and then cast it into a custom cell. The cell in your nib should already be a custom cell.
In the Apple sample the loading of the cell is much more straight forward. You link your custom cell to an IBOutlet in your view controller and then do this:
CustomCell *cell = (CustomCell *) [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = customTableViewCell;
self.customTableViewCell = nil;
// etc.
}
No, it is not okay to not release the label. You declared the property with 'retain'-specifier. That means you will have to release it at least in dealloc (A safe way to do that is: self. levelLabel = nil;).
As you've noticed the memory consumption will raise during scrolling if you don't release the objects (memory leaks!).
You'll have to tell where the exc_bad_access error occurs so that we can help you ...

Manage Multiple ViewControllers from a TableView

I'm curious if anyone has ideas for managing multiple ViewControllers from a TableView. I have a list of roughly seven items I am displaying in a TableView with a ViewController dedicated to each. My first thought is to initialize an array with the various ViewControllers.
NSMutableArray *viewControllers = [[NSMutableArray alloc] initWithCapacity:7];
[viewControllers addObject:[[ViewController1 alloc] initWithNibName:#"View1" bundle:nil]];
[viewControllers addObject:[[ViewController2 alloc] initWithNibName:#"View2" bundle:nil]];
...
Then reference that array to load the appropriate view on item selection.
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self.navigationController pushViewController:[viewControllers objectAtIndex:indexPath.row] animated:YES];
}
I'm really not sure if this is an appropriate approach. Any direction would be great.
EDITED:
Based on the feedback from Ryan and Joe I implemented an object to hold my table items. Abbreviating my problem also caused some confusion on implementation details. Added the full solution to manage both view controllers and selecting tab bar items.
TableNavigationItem.h
#import
#interface TableNavigationItem : NSObject {
NSString *title;
NSNumber *tabIndex;
id viewController;
}
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSNumber *tabIndex;
#property (nonatomic, retain) id viewController;
#end
TableNavigationItem.m
#import "TableNavigationItem.h"
#implementation TableNavigationItem
#synthesize title;
#synthesize viewController;
- (id) init{
if(self = [super init]){
self.title = #"";
}
return self;
}
- (void) dealloc {
[title release];
[tabIndex release];
[viewController release];
[super dealloc];
}
#end
Then initialize per Joe's suggestion.
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:7];
TableNavigationItem *navItem;
// view 1
navItem = [[TableNavigationItem alloc] init];
navItem.title = #"View 1";
navItem.tabIndex = [NSNumber numberWithInt:1];
[mutableArray addObject:navItem];
[navItem release];
// view 2
navItem = [[TableNavigationItem alloc] init];
navItem.title = #"View 2";
navItem.viewController = [ViewController2 class]];
[mutableArray addObject:navItem];
[navItem release];
...
// store the navigation items
self.tableItems = [NSArray arrayWithArray:mutableArray];
[mutableArray release];
Then
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
TableNavigationItem *navItem = [tableItems objectAtIndex:indexPath.row];
if(navItem.viewController != nil){
[self.navigationController pushViewController:[[[navItem.viewController alloc] init] autorelease] animated:YES];
}
else if(navItem.tabIndex != nil){
[((MyAppDelegate *)[UIApplication sharedApplication].delegate).tabBarController setSelectedIndex:[navItem.tabIndex integerValue]];
}
}
If all the views those controllers manage are visible on the screen immediately, there is nothing wrong with that approach. Make sure you release the array of VC's in -viewDidUnload, and recreate it in -viewDidLoad, so the runtime can unload all those extra objects when the next view is pushed onscreen. And be aware, only the root view controller will receive view lifecycle events; The view controllers you create and manually add the owned views to the table will not get those methods called. You'll have to implement some plumbing to get those view lifecycle events into the 'subviews', through notification or delegation.
The best answer to your question is "Instrument it". Run the Allocations and VM instruments at a minimum, and check to see how much memory those view controllers are consuming. If you want to improve your skillz with Instruments, watch the Performance session from WWDC 2011, they did a great job teaching how to use it to find memory and performance issues.
That sounds fine to me. The only concern I would have is whether your view controllers are RAM-heavy, in which case you may want to make a decision: is it better to preallocate everything (i.e. are you sure you can fit all of those controllers' state within available memory?) or is it better to take the latency hit to load the appropriate view controller as-needed?
It looks like your ViewControllers are of different classes. If that's the case (and if each one always uses the same respective nib), I would consider implementing a custom -init method on each and making your array of choices one of Class objects. That's just a matter of personal preference, though.
One more thing: You will want to autorelease those view controllers or you'll leak memory no matter what.

Loading problem of NIB file from TabViewController in iPhone

I have a UITableViewController (MyViewController.xib). This is showing 3 rows with their title. I have 3 new xib file for each row title.On each row selection I want to load XIB file. I am getting the place when I am clicking on RowIndex Selection. But when i am trying to load NIB file nothing is happening. I mean nither program is being crashed nor NIB file is being load.
I am defining my interface declaration here.
#import <UIKit/UIKit.h>
#import "HistoryShow.h"
#interface MyViewController : UITableViewController {
NSArray *tableList;
IBOutlet HistoryShow *historyController;
}
#property (nonatomic,retain) NSArray *tableList;
#property (nonatomic,retain) IBOutlet HistoryShow *historyController;
#end
My implementation details are below.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *str = [NSString stringWithFormat:#"%#",[tableList objectAtIndex:indexPath.row]]; //typecasting
if([#"History" isEqual:str])
{
NSLog(#"!!!!!!!!!!!!!!!");
HistoryShow *detailViewController = [[[HistoryShow alloc]initWithNibName:#"HistoryShow" bundle:nil]autorelease];
[historyController release];
}
}
This is prining "!!!!!!" on console but next "HistoryShow.xib" is not being load.
What is the exact problem ?
Thanks in advance.
You have to add the view to your present view using addSubview: or push the viewController using a navigationController to see the view.
Something like this
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *str = [NSString stringWithFormat:#"%#",[tableList objectAtIndex:indexPath.row]]; //typecasting
if([#"History" isEqual:str])
{
NSLog(#"!!!!!!!!!!!!!!!");
HistoryShow *detailViewController = [[HistoryShow alloc]initWithNibName:#"HistoryShow" bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES]; // if you have a navigation controller
[detailViewController release];
}
}
You are instantiating detailViewController, but you aren't doing anything with it.
Try adding this after the alloc of detailViewController:
[self presentModalViewController:detailViewController animated:YES];

NSArray causing memory leak when simulated low memory warning

I have made a sample project that reproduces this issue which contains two views:
root header:
#import <UIKit/UIKit.h>
#import "view2.h"
#interface RootViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>{
view2 *secondView;
UITableView *table;
NSArray *array;
}
#property (nonatomic, retain) view2 *secondView;
#property (nonatomic, retain) IBOutlet UITableView *table;
#property (nonatomic, retain) NSArray *array;
#end
root main:
#import "RootViewController.h"
#implementation RootViewController
#synthesize table, array, secondView;
- (void)viewDidLoad
{
[super viewDidLoad];
if(self.array == nil){
self.array = [NSArray arrayWithObjects:#"1", #"2", #"3", #"4", nil];
}
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload
{
[super viewDidUnload];
table = nil;
array = nil;
secondView = nil;
}
- (void)dealloc
{
[table release];
[array release];
[secondView release];
[super dealloc];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [array count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [array objectAtIndex:indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (secondView == nil) {
secondView = [[view2 alloc] init];
}
[self.navigationController pushViewController:secondView animated:YES];
}
#end
view2 simple contains a label with the text "view 2" for identification purposes.
All this code is doing in the root controller is creating an array with the values 1,2,3,4 and binding this text as rows to the table, clicking any row pushes view 2 onto the stack.
if you load up the app in the simulator using the leaks instruments tool, click on any row so that view2 is displayed and then simulate an error warning the following leaks appear:
image
for the line:
self.array = [NSArray arrayWithObjects:#"1", #"2", #"3", #"4", nil];
this is causing me a lot of problems in my main app as i am using arrays to provide data in tables all over the place.
i have tried various ways to fix this such as declaring the array in different ways to no avail.
any help is greatly appreciated!
thanks
In viewDidUnload you're mixing up property vs. direct ivar access.
array = nil simply sets the ivar to nil without using the synthesized accessor method. You have to use the dot notation: self.array = nil;
This way the accessor setArray: is used which handles memory management for you.
Mixing up ivars and properties is a frequent problem amongst Objective-C beginners. The confusion can easily be prevented by always using different names for properties and ivars:
#synthesize array = _array;
You can just leave out the ivar declaration in the class's #interface or name it as in the #synthesize directive.

iPhone Xcode - Navigation Controller on second xib view?

Everything is fine, my navigation controller display's my 'Menu 1' item, but when i click it there appears to be a problem with the:
[self.navigationController pushViewController:c animated:YES]; line it doesn't connect to the break point in the myClass file. so I think i've not joined something? but unsure what?
My second view with the navigation controller doesn't have direct access to the AppDelegate so can't join it like I see in some tutorials.
1st view is just a button when clicked calls:
[self presentModalViewController:mainViewController animated:YES];
my second View 'MainViewController' header looks like:
#interface MainViewController :UITableViewController <UITableViewDelegate, UITableViewDataSource>
{
NSArray *controllers;
UINavigationController *navController;
}
#property (nonatomic, retain) IBOutlet UINavigationController *navControllers;
#property (nonatomic, retain) NSArray *controller;
Then I have my MainViewController.m
#synthesize controllers;
#synthesize navController;
- (void) viewDidLoad
{
NSMutableArray *array = [[NSMutaleArray alloc] init];
myClass *c = [[myClass alloc] initWithStyle:UITableViewStylePlain];
c.Title = #"Menu 1";
[array addObject:c];
self.Controllers = array;
[array release];
}
implemented numberOfRowsInSection and cellForRowAtIndexPath
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
myClass *c = [self.controllers objectAtIndex:row];
[self.navigationController pushViewController:c animated:YES]; // doesn't load myClass c
// [self.navController pushViewController:c animated:YES];
}
Also in Interface Builder I dragged a Navigation Controller onto my new XIB and changed the Root View Controller class to MainViewController and also connected the File Owner connector to the Navigation Controller to connect the navController Outlet.
Thanks for you time.
myClass.h
#import "SecondLevelViewController.h" //This inherts UITableViewController
#class myClass;
#interface myClass : SecondLevelViewController
{
NSArray *list;
myClassDetail *detail;
}
#property (nonatomic, retain) NSArray *list;
myClass.m
#import "myClass.h"
#import "myClassDetail.h"
#import "NavAppDelegate.h"
#implementation myClass
#systjesize list;
- (void) viewDidLoad
{
NSArray *array = [[NSArray alloc] initWithObjects:#"test1",#"test2",nil];
self.list = array;
.. .. ..
//I can't get a break point at this point or in any of the other methods
}
So not getting a break point to hit in this page tells me I've missed sometihng. With this being a seperate XIB file from the MainWindow.XIB, I don't have access to the App Delegate.
So really I need to know how to wire Navigation Controller to a second view XIB file when I don't have a App delegate in the interface builder. All the tutorials show the navigation controller being connected to this app delegate.
The program complies file and runs, I get the 1st 'Menu 1' in the list but then when I try and re populate the same navigation list with my new myClass menu items 'test 1', 'test 2'
it doesn't hit the event viewDidLoad.