views:

104

answers:

2

OK, for two days now i have been trying to solve an error i have inside the cellForRowAtIndex method, let start by saying that i have tracked down the bug to this method, the error is [CFDictionary image] or [Not a Type image] message sent to deallocated instance.

I know about the debug flags, NSZombie, MallocStack, and others, they helped me narrow it down to this method and why, but I do not know how to solve besides a redesign of the app UI.

SO what am i trying to do, well for this block of code, displays a purchase detail, which contains items, the items are in there own section, now when in edit mode, there appears a cell at the bottom of the items section with a label of "Add new Item", and this button will present a modal view of the add item controller, item is added and the view returns to the purchase detail screen, with the just added item in the section just above the "add new Item" cell, the problem happens when i scroll the item section off screen and back into view the app crashes with EXC_BAD_ACCESS, or even if i don't scroll and instead hit the back button on the navBar, still the same error.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
    UITableViewCell *cell   = nil;

    switch (indexPath.section) 
    {
        case PURCHASE_SECTION:
        {   
            static NSString *cellID = @"GenericCell";

            cell = [tableView dequeueReusableCellWithIdentifier:cellID];

            if (cell == nil)
            {
                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 
                                               reuseIdentifier:cellID] autorelease];
                cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
            }

            switch (indexPath.row) 
            {
                case CATEGORY_ROW:
                    cell.textLabel.text         = @"Category:";
                    cell.detailTextLabel.text   = [self.purchase.category valueForKey:@"name"];
                    cell.accessoryType          = UITableViewCellAccessoryNone;
                    cell.editingAccessoryType   = UITableViewCellAccessoryDisclosureIndicator;

                    break;
                case TYPE_ROW:
                    cell.textLabel.text         = @"Type:";
                    cell.detailTextLabel.text   = [self.purchase.type valueForKey:@"name"];
                    cell.accessoryType          = UITableViewCellAccessoryNone;
                    cell.editingAccessoryType   = UITableViewCellAccessoryDisclosureIndicator;

                    break;
                case VENDOR_ROW:
                    cell.textLabel.text         = @"Payment:";
                    cell.detailTextLabel.text   = [self.purchase.vendor valueForKey:@"name"];
                    cell.accessoryType          = UITableViewCellAccessoryNone;
                    cell.editingAccessoryType   = UITableViewCellAccessoryDisclosureIndicator;

                    break;
                case NOTES_ROW:
                    cell.textLabel.text         = @"Notes";
                    cell.editingAccessoryType   = UITableViewCellAccessoryNone;

                    break;
                default:
                    break;
            }
            break;
        }
        case ITEMS_SECTION:
        {

            NSUInteger  itemsCount = [items count];

            if (indexPath.row < itemsCount) 
            {
                static NSString *itemsCellID = @"ItemsCell";

                cell = [tableView dequeueReusableCellWithIdentifier:itemsCellID];

                if (cell == nil)
                {
                    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 
                                                   reuseIdentifier:itemsCellID] autorelease];
                    cell.accessoryType = UITableViewCellAccessoryNone;
                }

                singleItem                  = [self.items objectAtIndex:indexPath.row];
                cell.textLabel.text         = singleItem.name;
                cell.detailTextLabel.text   = [singleItem.amount formattedDataDisplay];
                cell.imageView.image        = [singleItem.image image];

            } 
            else
            {
                static NSString *AddItemCellID = @"AddItemCell";

                cell = [tableView dequeueReusableCellWithIdentifier:AddItemCellID];

                if (cell == nil) 
                {
                    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                                   reuseIdentifier:AddItemCellID] autorelease];
                    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
                }

                cell.textLabel.text = @"Add Item";
            }
            break;
        }
        case LOCATION_SECTION:
        {
            static NSString *localID = @"LocationCell";

            cell = [tableView dequeueReusableCellWithIdentifier:localID];

            if (cell == nil)
            {
                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 
                                               reuseIdentifier:localID] autorelease];
                cell.accessoryType = UITableViewCellAccessoryNone;
            }

            cell.textLabel.text         = @"Purchase Location";
            cell.accessoryType          = UITableViewCellAccessoryDisclosureIndicator;
            cell.editingAccessoryType   = UITableViewCellAccessoryNone;
            break;
        }
        default:
            break;
    }
    return cell;
}

the singleItem is of Modal Type PurchaseItem for core data

now that i know what is causing the error, how do i solve it, I have tried everything that i know and some of what i dont know but still, no progress, please any suggestions as to how to solve this without redesign is my goal, perhaps there is an error i am doing that I cannot see, but if it's the nature of autorelease, than i will redesign.

UPDATE: adding the AddItemController

//
//  AddItemViewController.m
//  spendTrac
//
//  Created by iAm on 3/5/10.
//  Copyright 2010 heariamStudios. All rights reserved.
//

#import "AddItemViewController.h"
#import "Purchase.h"
#import "PurchaseItem.h"
#import "FormattedDataDisplay.h"
#import "ModalAlert.h"
#import "UtilityBelt.h"

@implementation AddItemViewController

@synthesize purchase, item, itemAmount, itemImage;
@synthesize amountPadBtn, nameAlertBtn, addImageBtn;
@synthesize amountLab, itemNameLab;
@synthesize itemImageView;


#pragma mark -
#pragma mark IBAction Methods

- (void)cancel:(id)sender 
{
    [self.navigationController popViewControllerAnimated:YES];
}

- (void)save:(id)sender 
{
    NSManagedObjectContext *context = purchase.managedObjectContext;

    if (!item)
    {
        itemImage   = [NSEntityDescription insertNewObjectForEntityForName:@"Image"         inManagedObjectContext:context];
        item        = [NSEntityDescription insertNewObjectForEntityForName:@"PurchaseItem"  inManagedObjectContext:context];
        [purchase addItemsObject:item];
        item.displayOrder = [NSNumber numberWithInteger:[purchase.items count]];

    }


    NSString        *stringAmt  = [UtilityBelt charStripper:self.amountLab.text];
    NSDecimalNumber *amount     = [NSDecimalNumber decimalNumberWithString:stringAmt];

    [itemImage setValue:self.itemImageView.image forKey:@"image"];

    item.image  = itemImage; 
    item.name   = itemNameLab.text;
    item.amount = amount;

    NSError *error = nil;
    if (![context save:&error]) 
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    [self.navigationController popViewControllerAnimated:YES];
}

- (void)amountPadButtonTapped:(id)sender
{
    AmountPad *amountPad = [[AmountPad alloc]initWithNibName:@"AmountPad" bundle:nil];
    amountPad.delegate = self;
    [self.navigationController presentModalViewController:amountPad animated:YES];
    [amountPad release];
}

- (void)nameAlertButtonTapped:(id)sender 
{
    NSString *answer = [ModalAlert ask:@"Name This Item" withTextPrompt:@"item name"];

    if (answer) 
    {
        self.itemNameLab.text = [NSString stringWithFormat:@"%@", answer];
    } 
    else 
    {
        [ModalAlert say:@"What, You Don't Know!"];
    }
}


- (void)photoButtonTapped:(id)sender
{
    UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
    imagePicker.delegate        = self;
    imagePicker.allowsEditing   = YES;
    imagePicker.mediaTypes      = [UIImagePickerController availableMediaTypesForSourceType:imagePicker.sourceType];
    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
    {
        imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
    }
    else {
        imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    }
    [self presentModalViewController:imagePicker animated:YES];
    [imagePicker release];

}

- (void)imagePickerController:(UIImagePickerController *)picker 
        didFinishPickingImage:(UIImage *)selectedImage 
                  editingInfo:(NSDictionary *)editingInfo 
{
    // Create a thumbnail version of the image for the item object.
    CGSize size = selectedImage.size;
    CGFloat ratio = 0;
    if (size.width > size.height) {
        ratio = 277.3 / size.width;
    } else {
        ratio = 277.3 / size.height;
    }
    CGRect rect = CGRectMake(0.0, 0.0, ratio * size.width, ratio * size.height);

    UIGraphicsBeginImageContext(rect.size);
    [selectedImage drawInRect:rect];
    self.itemImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    [self dismissModalViewControllerAnimated:YES];
}


- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker 
{
    [self dismissModalViewControllerAnimated:YES];
}

#pragma mark -
#pragma mark View Controller Methods

- (void)viewDidLoad 
{   
    if (self.item) 
    {
        self.itemImage              = self.item.image;
        self.itemImageView.image    = [self.item.image image];
        self.itemNameLab.text       = self.item.name;
        self.amountLab.text         = [self.item.amount formattedDataDisplay];
    }

    UINavigationItem *navigationItem = self.navigationItem;
    navigationItem.title = @"Item";

    UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel 
                                                                                  target:self 
                                                                                  action:@selector(cancel:)];
    self.navigationItem.leftBarButtonItem = cancelButton;
    [cancelButton release];

    UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithTitle:@"Save" 
                                                                   style:UIBarButtonSystemItemSave 
                                                                  target:self 
                                                                  action:@selector(save:)];
    self.navigationItem.rightBarButtonItem = saveButton;
    [saveButton release];
    [super viewDidLoad];
}

- (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.
}

- (void)viewDidUnload
{
    // Release any retained subviews of the main view.
    self.amountLab      = nil;
    self.itemNameLab    = nil;
    self.addImageBtn    = nil;
    self.nameAlertBtn   = nil;
    self.amountPadBtn   = nil;

    [super viewDidUnload];
}


#pragma mark -
#pragma mark AmountPadDelegate Method

- (void)amountPadDidPressDoneButton:(NSString *)amount
{
    NSDecimalNumber *itemAmt = [NSDecimalNumber decimalNumberWithString:amount];
    self.amountLab.text = [itemAmt formattedDataDisplay];
    [self dismissModalViewControllerAnimated:YES];
}



#pragma mark -
#pragma mark Memory Management

- (void)dealloc 
{
    [addImageBtn    release];   
    [nameAlertBtn   release];
    [amountPadBtn   release];

    [itemAmount     release];
    [purchase       release];
    [item           release];
    [itemImage      release];

    [itemImageView  release];

    [amountLab      release];
    [itemNameLab    release];
    [super dealloc];
}


@end
A: 

Something having to do with your add item controller may be over-releasing the image in the singleItem you added. It then gets dealloced when the cell scrolls offscreen and is reused, then crashes when it's redisplayed. If that's true, the problem would lie outside -cellForRowAtIndexPath:.

If you don't see anything obvious, you could put continuing breakpoints on [PurchaseItem retain] and [PurchaseItem release] with the gdb command bt 3 (or so), and look at what's releasing it. You may instead have to look at retain and release of the image itself.

Did you try running the static code analyzer to see if it picks up a related memory management error?

One other thing: it seems odd to send the message -image to an image. What exactly is the class of a singleItem.image ?

Paul Collins
singleItem is a core data model class PurchaseItem, and "image" is a related attribute in that entity. I have an entity called "Image, and a reverse relationship back to PurchaseItem entity. Now "[singleItem.image image]" returns a UIimage, but singleItem.image returns a type of "Image". I hope this helps, also thanks for the response, you mentioned some things i did not try, i am still new at this stuff, so i will try the static code analyzer and see what that turns up. also i saved the data to the context in the add item controller so i should be able to release it in that controller, yes no
iAm
static analyzer did not return any problems, if you have any other suggestions i am open, The problem has to be in the [singleItem.image image] call because when i leave that out there is no error, but i still am too new to see the solution, i will however keep going and going just like the energizer bunny :), but i may need a push in the right direction ;)
iAm
how do i set a breakpoint on [PurchaseItem release], cause in the breakpoint window, i am using a symbol, it is grayed out, this message [PurchaseItem release] is not in my code. so how do test to see when it is called
iAm
A: 

Looking at your save: method in AddItemViewController, I don't see where item is retained. It's autoreleased so it needs to be retained somewhere. It's possible that:

[purchase addItemsObject:item] 

retains it, but I'd have to see that code.

EDIT:

Add this to your save: method:

[item retain];

I don't see how item is retained. Since it's autoreleased item will be a bad pointer, leading to your error. And since you say the error manifests iself when you unload/load the view, and viewDidLoad: references item, I think this is a good possibility. I'm not completely sure since I don't know the class hierarch of purchase.

Rob Jones
ok, singleItem is of type PurchaseItem which is a coreData entity, and yes it is a property of PurchaseDetailController, i did have self.singleItem, but removed it for a test. i also did add a retain message self.singleItem = [self.items objectAtIndex:indexPath.row] retain]; but still the same error. i can send you the two classes PurchaseDetail and AddItem if you have the time to take a look, let me know
iAm
When you add an item, does it get added to the items array?
Rob Jones
Just add the relevant code from the other classes to your question.
Rob Jones
ok, add the class for you, again thank u for taking the time to help me, i very grateful.
iAm
Changed my "answer" based on the new code.
Rob Jones
the addItemsObject:item is a auto coredata generated method, how would i retain it then?
iAm
What is purchase's class?
Rob Jones
YEAH!, YEAH!, THANK YOU THANK YOU, Rob, you are the best, thank you, you found the solution, i did need to retain it, so i did thisitem.image = [itemImage retain];and it worked and did not crash. Again thank you very much
iAm
Based on that information, does your item.image property have the 'retain' attribute set? You know, I just kinda assumed that image was retained because it was set using dot notation. That was a dangerous assumption on my part, and it would explain why retaining itemImage solved your problem.
Rob Jones
I ran into some other problems that i think pertain to this and will update the post after my investigation, but you did point me in the right direction.
iAm
it seems that i need to retain the itemNameLab.text as well, problem solved, i am moving on with the app, again thank you for your valuable time. Perhaps maybe next time I can be as much help too you, as you have been to me.
iAm
i just wanted to add that [item retain] causes this error [CFSet retain] message sent to deallocated instance, so i just retain the image and the item name and not the NSDecimalNumer or amount.
iAm