Memory Management Advice - objective-c

In my root view, I have a tableview with three rows. When I click either row, it will present a new view and then I can press the back button that is automatically created by my nav controller. My problem arises when I try to click a row that has already been chosen before. I get an EXC BAD ACCESS error message. I think this is all the code that is neccessary:
- (void)dealloc
{
self.rowChosenArray = nil;
self.rootChoicesArray = nil;
self.customImage = nil;
self.rootTableView = nil;
[super dealloc];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.rootChoicesArray = [[[NSMutableArray alloc] initWithObjects:#"",#"See Today's Deals!", #"My Purchased Deals", #"Personal Settings", nil] autorelease];
self.rowChosenArray = [[[NSMutableArray alloc] initWithObjects:#"", nil] autorelease];
DealsViewController *dealsViewController = [[DealsViewController alloc] initWithNibName:#"DealsViewController" bundle:nil];
[self.rowChosenArray addObject:dealsViewController];
[dealsViewController release];
PurchasedDealsViewController *purchasedDealsViewController = [[PurchasedDealsViewController alloc] initWithNibName:#"PurchasedDealsViewController" bundle:nil];
[self.rowChosenArray addObject:purchasedDealsViewController];
[purchasedDealsViewController release];
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
[self.rowChosenArray addObject:settingsViewController];
[settingsViewController release];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *targetViewController = [rowChosenArray objectAtIndex: indexPath.row];
[[self navigationController] pushViewController:targetViewController animated:YES];
[targetViewController release];
}
I am still unfamiliar where/when to release/set objects to nil (all four of my objects have the retain property in the header file.
Thank you in advance for the help, let me know if there is anything else you may need.

Before you add an element into an array, you alloc it, but release it once it is added - so there is no leak;
You never need to release the elements in the array, because they will be released at the time when you release the array itself.

You are over-releasing targetViewController in tableView:didSelectRowAtIndexPath:. You get a non-owning reference through objectAtIndex: (which means that you are not responsible for that reference) and may not release that reference. So just remove [targetViewController release]; and everything's fine.
The rule is like this: you may only release a reference if you either retained it or got it through a method whose name:
is or starts with alloc, copy or mutableCopy
starts with new

also you have some leaks here, if you retain in viewDidLoad you should release/nil in viewDidUnload and in dealloc

Related

Instance variables not working in ViewController in UINavigationController

I'm still new to iOS development but I've ran into a problem that I can't solve and I've tried looking online but can't find anything yet.
I'm using a UIImagePickerController to pick and image and I'm using it in the App Delegate. When an image is returned, in the imagePickerController:didFinishPickingMediaWithInfo method, I want to make a new navigation controller with a view controller and put it over the "app".
Here is how I'm doing it:
CustomNavigationController *customNavigationController = [[CustomNavigationController alloc] init];
PhotoViewController *photoViewController = [[PhotoViewController alloc] initWithNibName:#"PhotoViewController" bundle:[NSBundle mainBundle]];
[customNavigationController pushViewController:photoViewController animated:NO];
[customNavigationController.view setFrame:[[UIScreen mainScreen] applicationFrame]];
[photoViewController release];
[self.window addSubview:customNavigationController.view];
//Call method of photoViewController.
[[customNavigationController.viewControllers objectAtIndex:0] addPhotoFromData:info];
[self.tabBarController dismissViewControllerAnimated:YES completion:nil]; //Get rid of UIImagePickerController
However in the photoViewController, I don't have access to any instance variables synthesized and loaded in viewDidLoad. They all return to null. There is also a tableView and calls to reload the tableView do not actually cause the tableView to respond.
Here is some of the code from photoViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.photoCount = 0;
self.timestamp = [[NSDate date] timeIntervalSince1970];
self.photos = [[NSMutableArray alloc] initWithCapacity:5];
self.photosData = [[NSMutableArray alloc] initWithCapacity:5];
self.uploadingCount = 0;
UINib *customCell = [UINib nibWithNibName:#"CustomTableCell" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:customCell forCellReuseIdentifier:#"customCell"];
self.tableView.separatorColor = [UIColor clearColor];
NSLog(#"%#", self);
}
and also the addPhotoFromData method:
- (void)addPhotoFromData:(NSDictionary *)info
{
[self.photos addObject:info];
NSLog(#"%#", self.photos); //Doesn't add "info" and does not return anything when later called from other methods.
[self.tableView reloadData]; //Doesn't work
Everything was working before I add in the UINavigationController. I'm completely lost.
EDIT: After some more debugging attempts, I have discovered that the view is not loaded when addPhotoFromData is called. viewDidLoad is called afterwords. Is there a way to delay method calls?
Any reason you can't have the PhotoViewController call addPhotoFromData: inside the viewDidLoad method? You can always access properties of the app delegate from the view controller:
YourAppDelegateType * delegate = [UIApplication sharedApplication].delegate;
[self addPhotoFromData:delegate.info];

DetailView not opening from UITableViewController press (iOS Xcode)

Inside my TableView, I am pressing one of the cells to enter the detailed view, and for some reason it won't take me to the detailed view. Does anyone see a problem in the below code?
For context, this is a teacher directory, and pressing one of the cells brings up the teachers picture and other info.
Thanks for the help...if you need any more info I can add it in.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Get the selected teacher
NSDictionary *dictionary = [listOfItems objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"Teachers"];
NSString *selectedTeacher = [array objectAtIndex:indexPath.row];
//Initialize the detail view controller and display it.
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:[NSBundle mainBundle]];
dvController.selectedTeacher = selectedTeacher;
[self.navigationController pushViewController:dvController animated:YES];
dvController = nil;
}
You should check if dvController is correctly loaded, so try to NSLog dvController once you have called the alloc-init. Another way to instantiate it is to use this simple call which works if you created the view controller and the xib together:
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:nil bundle:nil];
Besides there is no reason to nil dvController at the end. If you're concerned with memory management, that is you don't want to leak dvController, simply autorelease it. So replace:
dvController=nil;
with:
[dvController autorelease];
this works because the navigation controller retains the pushed view controller
(or use ARC).
Finally I assume the tableView:didSelectRowAtIndexPath: is called... if not sure, just a place a breakpoint.
I don't know if you ever resolved your problem, but I was having the same problem where my Detail View was not appearing. I finally replaced
[self.navigationController pushViewController:dvController animated:YES];
with
[self presentModalViewController:dvController animated:YES];
and it worked. Hope that helps.

addObject: to array not working (array still nil)

This app is a table view with a tab bar controller. I am logging the count of the array: arrayOfFavourites and even though i add an object is continues to have a nil value, my relating code, all objects shown are allocated and initialized in the code (previous or present) some are instances and some are properties:
ListViewController.m:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"TOUCHED CELL!");
// Push the web view controller onto the navigation stack - this implicitly
// creates the web view controller's view the first time through
[[self navigationController] pushViewController:webViewController animated:YES];
// Grab the selected item
entry = [[channel items] objectAtIndex:[indexPath row]];
if (!entry) {
NSLog(#"!entry");
}
// Construct a URL with the link string of the item
NSURL *url = [NSURL URLWithString:[entry link]];
// Construct a request object with that URL
NSURLRequest *req = [NSURLRequest requestWithURL:url];
// Load the request into the web view
[[webViewController webView] loadRequest:req];
// Take the cell we pressed
// IMPORTANT PART
CELL = [tableView cellForRowAtIndexPath:indexPath];
[webViewController setItem:entry];
webViewController = nil;
webViewController = [[WebViewController alloc] init];
[entry release];
}
WebViewController.m:
You shake to favorite a cell
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
cellToPassOn = nil;
NSLog(#"Favouriting"); // YES I KNOW SPELLING
// This is pretty simple, what we do is we take the cell we touched and take its title and link
// then put it inside an array in the Favourites class
Favourites *fav = [[Favourites alloc] init];
ListViewController *list = [[ListViewController alloc] init];
[self setCellToPassOn: [list CELL]];
if (!item) {
NSLog(#"NILLED ITEM");
}
[[fav arrayOfFavourites] addObject:[item autorelease]];
[fav setCell: cellToPassOn];
[fav release];
[list release];
item = nil;
}
Favourites.m:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
arrayOfFavourites = [[NSMutableArray alloc] init];
NSLog(#"ROWS NO.");
NSLog(#"%i", [arrayOfFavourites count]);
return [arrayOfFavourites count];
}
Why are you inializing the array in tableview:numberOfRowsInSection ? This will cause the array to be reset each time table view is reloaded. This could be your issue.
you are allocating your array in -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
try to allocate it somewhere else.
You could allocate the arrayOfFavorites in the tableView:numberOfRowsInSectionMethod, but then you first need to check if it is nil.
if( !arrayOfFavorites )
arrayOfFavoriges = [[NSMutableArray alloc] init];
You should release it then in the dealloc method: [arrayOfFavorites release].
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
cellToPassOn = nil;
NSLog(#"Favouriting"); // YES I KNOW SPELLING
// HERE creation of a Brand NEW empty Favourites instance
Favourites *fav = [[Favourites alloc] init];
// HERE creation of a Brand NEW empty ListViewController instance
ListViewController *list = [[ListViewController alloc] init];
// HERE we hope that the ListViewController as CELL other then nil when it is Brand NEW
[self setCellToPassOn: [list CELL]];
if (!item) {
NSLog(#"NILLED ITEM");
}
[[fav arrayOfFavourites] addObject:[item autorelease]];
[fav setCell: cellToPassOn];
[fav release];
// HERE the fav instance get deallocated and don't exist anymore
[list release];
// HERE the list instance get deallocated and don't exist anymore
item = nil;
}
In this code list and fav exist only in the body of this method, attempt to get to the value they have hold done to will failed, because list and fav doesn't exist outside that method.

Can someone explain to me why I "need" a retain statement in the following segment of code?

I believe in learning along the process and the following code segment is not very clear to me. I know that an alloc statement would increase the retain count however there are certain aspects of iOS development that are still very confusing to me.
Why do I need a : [jokesArray retain]; in the following code segment?
I have a jokesArray = [[NSArray alloc]init]; and from what I have read is enough to retain?
Can someone please explain in an easy to understand manner why that retain statement is needed? (Else the app crashes with a EXC_Bad_Access.
I have had some kind people try to explain but it has not worked. Any help will be greatly appreciated.
#import "JokesViewController.h"
#implementation JokesViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
jokesArray = [[NSArray alloc]init];
[self getJokes];
}
- (void)viewDidUnload
{
[super viewDidUnload];
[jokesArray release];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [jokesArray 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]setText:[[jokesArray objectAtIndex:0]objectForKey:#"text"]];
// [[cell textLabel]setText:#"ok"];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
}
#pragma mark - Custom Functions
-(void) getJokes
{
NSURL *url = [NSURL URLWithString:#"someurl"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
NSDictionary *resultsDictionary = [responseString objectFromJSONString];
jokesArray = [resultsDictionary allValues];
[jokesArray retain]; //WHY DO I NEED THIS?
[self.tableView reloadData];
NSLog(#"%#", [jokesArray description]);
// Use when fetching binary data
// NSData *responseData = [request responseData];
}];
[request setFailedBlock:^{
NSError *error = [request error];
}];
[request startAsynchronous];
}
#end
You are leaking your previous NSArray allocation because in this part of the code:
NSDictionary *resultsDictionary = [responseString objectFromJSONString];
jokesArray = [resultsDictionary allValues];
[jokesArray retain];
You are creating an NSDictionary and replacing whatever jokesArray was pointing to point to the data from NSDictionary that you just created.
Also the data returned by NSDictionary was created using a convenience initialization method which means it will get released after a while thus the reason why you need to retain.
Since you are modifying the jokesArray variable directly the previously allocated NSArray doesn't get released when you replace it with the new object.
As far as I can tell, jokesArray does get retained on init.
However, since you have this line:
jokesArray = [resultsDictionary allValues];
You assign a complete different object to the variable, hence you retain a whole different object. I assume you would want something more like:
[jokesArray addObjectsFromArray:[resultsDictionary allValues]];
if its a mutable array. if not, you will have to to initalize a new one. In this case, I would probably only initialize jokesArray when needed.
You have this:
jokesArray = [[NSArray alloc]init];
Separately, you have this:
jokesArray = [resultsDictionary allValues];
The 2nd call, calling allValues, is allocating a brand new jokesArray. The one you'd already allocated is now lost (assuming jokesArray isn't a retained property) and you ought to release it before re-assigning via the allValues line.
The reason you need a 'retain' after the allValues call is because the memory allocated in allValues will be marked for autorelease. If you want that memory to stick around (and it appears you do), you need to retain it. Then your call to release in viewDidUnload has something to release, and your other references to jokesArray (e.g. the count call) have some memory to act against.
Switching to using a retained property will save you from all this hassle.

Error: unrecognized selector sent to instance

I have a custom object, Spaces:
#import "Spaces.h"
#implementation Spaces
#synthesize spaceName;
#synthesize spaceUsers;
#synthesize spaceIcon;
#synthesize spaceID;
#synthesize imageURLString;
- (void)dealloc
{
[spaceName release];
[spaceUsers release];
[spaceIcon release];
[imageURLString release];
[super dealloc];
}
#end
My root view controller implements a cellForRowAtIndexPath: and grabs from an NSArray of Spaces:
[[cell spaceName] setText:aSpace.spaceName];
[[cell spaceChatType] setText:#"People"];
[[cell spaceActiveUsers] setText:aSpace.spaceUsers];
This works fine and I can click to go into the detail view and back to the list, but after maybe 5-6 clicks back and forth between the table view and detail view, I get an error at [[cell spaceName] setText:aSpace.spaceName]; which is
'-[__NSCFSet spaceName]: unrecognized selector sent to instance 0x6047b90'"
Please help! Any insight will be very appreciated!
UPDATE:
I'm still getting the same error but I've narrowed it down to the this:
-I'm creating a detail view controller on didSelectRowAtIndexPath...
-The detail view is being pushed to the viewcontroller and displays fine, I have a back button added as well.
-The detail view loads information and refreshes on a timer
-Pressing the back button goes back to the table list view
This is the problem my detail view is not being released from memory so the more I go back and forth between the views the more timers were going off simultaneously. I added a check to viewWillDisappear that stops the timer by setting a bool value.
I noticed that the detail view is not unloading...
From the RootViewController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//no longer on initial view
isinit = NO;
//hide keyboard
[spacesSearch resignFirstResponder];
if (spaces != nil && spaces.count > 0)
{
//set back button reference
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Spaces" style:UIBarButtonItemStylePlain target:self action:#selector(returnSpacesList:)];
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
DetailViewController *details = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
//Grab Data from selected Space object and pass to DetailViewController
Spaces *aSpace = nil;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
if ([self.filteredListContent count] == 0)
{
//self.lastSearchText
NSLog(#"Create new space code!");
}
else
{
aSpace = [self.filteredListContent objectAtIndex:indexPath.row];
}
}
else
{
aSpace = [spaces objectAtIndex:[indexPath row]];
}
//set title and display
self.navigationController.title = [NSString stringWithFormat:#"/%#/",aSpace.spaceName];
//pass data
[details passedValue:aSpace.spaceID :aSpace.spaceName];
[self.navigationController pushViewController:details animated:YES];
[aSpace release];
[details release];
}
}
How can I force the detail view to be released from memory?
Thank you
It sounds like [cell spaceName] has been autoReleased. I cannot see how you have defined that, but take a look at that part of your code.
If you need more help, you need to provide more code.
Perhaps your aSpace = [spaces objectAtIndex:[indexPath row]];
is not returning a Space object. Perhaps before you try and use it you test to make sure with something like if ([aSpace class] == [Spaces class])