1

I'm using a UITableViewController to display a list of articles from a web service. Once the data is retrieved this delegate method is called:

-(void)itemsDownloaded:(NSArray *)items
{
    // Set the items to the array
    _feedItems = items;

    // Reload the table view
    [self.tableView reloadData];
}

I'm also using a custom cell so that the label's height varies, therefore displaying the whole of the article's title with the following code (followed this tutorial Table View Cells With Varying Row Heights):

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = @"BasicCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
    [self configureCell:cell forRowAtIndexPath:indexPath];
    return cell;
}

- (void)configureCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if ([cell isKindOfClass:[CustomTableViewCell class]])
    {
        CustomTableViewCell *textCell = (CustomTableViewCell *)cell;

            Article *article_item = _feedItems[indexPath.row];

            NSString *fulltitle = article_item.Title;

//                fulltitle = article_item.Cat_Name; // testing category name

            if (article_item.Subtitle != nil && article_item.Subtitle.length != 0) {
                fulltitle = [fulltitle stringByAppendingString:@": "];
                fulltitle = [fulltitle stringByAppendingString:article_item.Subtitle];
            }
            textCell.lineLabel.text = fulltitle;

        textCell.lineLabel.numberOfLines = 0;
        textCell.lineLabel.font = [UIFont fontWithName:@"Novecento wide" size:12.0f];
    }
}



- (CustomTableViewCell *)prototypeCell
{
    NSString *cellIdentifier = @"BasicCell";

    if (!_prototypeCell)
    {
        _prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    }
    return _prototypeCell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self configureCell:self.prototypeCell forRowAtIndexPath:indexPath];

    self.prototypeCell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(self.prototypeCell.bounds));

    [self.prototypeCell layoutIfNeeded];

    CGSize size = [self.prototypeCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    return size.height+1;
}

The first issue is that the method forRowAtIndexPath is being called twice instead of once. Therefore if the _feeditems has 10 objects, the method is called 20 times. The second time the method is called I'm getting two properties (ID and Cat_Name) of the Article object null since of deallocation:

*** -[CFString retain]: message sent to deallocated instance 0x9c8eea0
*** -[CFNumber respondsToSelector:]: message sent to deallocated instance 0x9c8e370

This fires an EXC_BAD_ACCESS when trying to display the category name.

I'm not sure what can be the problem exactly, I've tried removing the code to vary the height of the labels to see if that was causing this problem by using this code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Retrieve cell
    NSString *cellIdentifier = @"BasicCell";
    UITableViewCell *myCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    // Get article
    Article *item = _feedItems[indexPath.row];

    myCell.textLabel.text = item.Title;

    return myCell;
}

The only difference was that the method was being called once meaning 10 times if _feeditems has 10 objects. But the Article's properties ID and Cat_Name were still being deallocated.

At the point of getting the data, all objects' properties in _feeditems are intact, nothing deallocated. I guess it's happening in cellForRowAtIndexPath or forRowAtIndexPath.

UPDATE

As suggested by @Ilya K. not calling configureCell:forRowAtIndexPath: from tableView:heightForRowAtIndexPath stopped the issue of having it being called twice. I've also tried having a property of feedItems. So far this was being set in the @interface of the Controller (TableViewController.m):

@interface TableViewController () {
    HomeModel *_homeModel;
    NSArray *_feedItems;
    Article *_selectedArticle;
}

I've removed it from the interface and added it as a property (TableViewController.h):

@interface TableViewController : UITableViewController <HomeModelProtocol>
@property (weak, nonatomic) IBOutlet UIBarButtonItem *sidebarButton;
@property (nonatomic, strong) CustomTableViewCell *prototypeCell;
@property(nonatomic) NSString *type;
@property(nonatomic) NSString *data;
@property(copy) NSArray *_feedItems;
@end

It's still giving deallocated messages though.

UPDATE 2

I've looked through the code using Instruments with a Zombie template (thanks to the answer of this question ViewController respondsToSelector: message sent to deallocated instance (CRASH)). This is the error I'm getting from Instruments:

Zombie Messaged

An Objective-C message was sent to a deallocated 'CFString (immutable)' object (zombie) at address: 0x10c64def0

enter image description here

All Release/Retain Event Types point to the following method, connectionDidFinishLoading, which is being used when the JSON data is retrieved from the web service and create Article objects for each article retrieved:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // Create an array to store the articles
    NSMutableArray *_articles = [[NSMutableArray alloc] init];

    // Parse the JSON that came in
    NSError *error;

    // Highlighted in blue
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:_downloadedData options:kNilOptions error:&error];
    NSArray *fetchedArr = [json objectForKey:@"result"];

    // Loop through Json objects, create question objects and add them to our questions array
    for (int i = 0; i < fetchedArr.count; i++)
    {
        NSDictionary *jsonElement = fetchedArr[i];

        // Create a new location object and set its props to JsonElement properties
        Article *newArticle = [[Article alloc] init];
        newArticle.ID = jsonElement[@"ID"];
        newArticle.Title = jsonElement[@"Title"];
        newArticle.Subtitle = jsonElement[@"Subtitle"];
        newArticle.Content = jsonElement[@"Content"];
        newArticle.ImageUrl = jsonElement[@"ImageUrl"];
        newArticle.Author = jsonElement[@"Author"];
        newArticle.PostId = jsonElement[@"PostId"];
        newArticle.ArticleOrder = jsonElement[@"ArticleOrder"];
        newArticle.Cat_Id = jsonElement[@"CategoryId"];
        // Highlighted in yellow
        newArticle.Cat_Name = jsonElement[@"CategoryName"];

        // Add this article object to the articles array
        // Highlighted in yellow
        [_articles addObject:newArticle];
    }

    // Ready to notify delegate that data is ready and pass back items
    if (self.delegate)
    {
        [self.delegate itemsDownloaded:_articles];
    }
}

I still can't figure out what is wrong though.

UPDATE 3

More testing on connectionDidFinishLoading I've removed the two properties that are being deallocated and no deallocated messages are shown. I don't know what's causing these two properties (ID and Cat_Name) to be deallocated, these are not being accessed from anywhere at this point.

Community
  • 1
  • 1
j.grima
  • 1,831
  • 3
  • 23
  • 45

2 Answers2

1

You don't need to call to configureCell:forRowAtIndexPath: from tableView:heightForRowAtIndexPath: you should determine cell height using Article object with sizeWithAttributes:

Your prototypeCell function just creates unrelated empty cell of type CustomTableViewCell and there is no point of trying re-size it.

tableView:cellForRowAtIndexPath: called each time your tableview needs redraw, when you scroll for example. That means that your _feeditems array should be allocated and consistent to work with UITableView at any point of instance life time.

Also make sure you declare a property for _feeditems and assign data using this property.

Example: @property (strong) NSArray *feeditems; or @property (copy) NSArray *feeditems;

in itemsDownloaded: self.feeditems = items;

Ilya K.
  • 940
  • 6
  • 16
  • Ok so it was being fired twice as it was called from `tableView:heightForRowAtIndexPath` as well. What I have so far is `feeditems` in the `@interface` of the `Controller`, not sure if that's enough. I'll try declaring a property as you're suggesting and see what happens. – j.grima Jun 07 '14 at 17:03
0

Finally solved the deallocated messages issue. While using Instruments with a Zombie template (using Instruments and Zombie template: ViewController respondsToSelector: message sent to deallocated instance (CRASH)) I found that this line:

NSDictionary *json = [NSJSONSerialization JSONObjectWithData:_downloadedData options:kNilOptions error:&error];

in the connectionDidFinishLoading method was causing this problem. I searched for NSJSONSerialization causing deallocation messages and I got the answer from this question Loading properties from JSON, getting "message sent to deallocated instance". The Article class had a few properties that were set to assign instead of strong:

@interface Article : NSObject

@property (nonatomic, assign) NSNumber *ID; // changed assign to strong
@property (nonatomic, strong) NSString *Title;
@property (nonatomic, strong) NSString *Subtitle;
@property (nonatomic, strong) NSString *Content;
@property (nonatomic, strong) NSString *ImageUrl;
@property (nonatomic, assign) NSNumber *PostId; // changed assign to strong
@property (nonatomic, strong) NSString *Author;
@property (nonatomic, assign) NSNumber *ArticleOrder; // changed assign to strong
@property (nonatomic, assign) NSNumber *Cat_Id; // changed assign to strong
@property (nonatomic, assign) NSString *Cat_Name; // changed assign to strong

@end

After changing the properties to strong, all deallocated messages stopped.

I know that this error seems to be very specific to each project and the cause of it may vary but in case someone has something similar, this is how I solved it.

Community
  • 1
  • 1
j.grima
  • 1,831
  • 3
  • 23
  • 45
  • Set object properties as strong or weak(related on what you want to achieve), assign used for simple type(int, float, char, etc). You really should read some basic introduction to objective c, it will safe you a lot of time and effort. – Ilya K. Jun 09 '14 at 08:08