Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView 'hidden' section causing more memory allocations every time on pull to refresh

I posted this on Code Review, but was told it may be better as a Stack Overflow question, so hence this post.

I have a UITableView that I'm 'hiding' a section when the user taps on that section's header. I thought this was a pretty cool implementation I came up with, but it turns out when this section is hidden and the user pulls to refresh, about 1-2MB is added into memory every time they pull to refresh (after the refresh finishes). If the section of the UITableView isn't hidden and they pull to refresh, no extra memory is allocated (the correct way/behavior). I know 1-2 MB isn't much but it all adds up. I could not find any leaks whatsoever in Profiler, so this is why I came here.

I may be overlooking something or my logic is flawed, and would appreciate any help offered. Sorry for the lengthy code below:

Update: resolved code:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    switch (section)
    {
        case 0:
        {
            return [self friendRequests];
        }
        case 1:
        {
            return [self friends];
        }
        default:
        {
            if (self.showingBlockedUsers == YES)
            {
                return [self blockedUsers];
            }
            else
            {
                return 0;
            }
        }
    }
}

Old code from original question:

- (void)refreshing:(UIRefreshControl *)refreshControl
{
    [refreshControl beginRefreshing];
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://foobar.com/test.plist"]];
    request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
    request.timeoutInterval = 5.0;

    [NSURLConnection sendAsynchronousRequest:request
                                   queue:[NSOperationQueue mainQueue]
                       completionHandler:
     ^(NSURLResponse *response, NSData *data, NSError *connectionError)
     {
         if (data)
         {
             NSPropertyListFormat format;

             NSMutableDictionary *dictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:&format error:nil];

             self.tableDataSource = dictionary;
             [self.tableView reloadData];
             [refreshControl endRefreshing];
             [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
         }
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell;
    NSString *identifier = @"cellIdentifier";
    UILabel *userNameLabel = [self userNameLabel];
    UIView *requestsView = [self requestsView];
    UIButton *approveButton = [self approveButton];
    UIButton *denyButton = [self denyButton];
    UIImageView *profileImageView = [self profileImageView];

    if (cell == nil)
    {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
        cell.selectionStyle = UITableViewCellSelectionStyleDefault;

        [requestsView addSubview:approveButton];
        [requestsView addSubview:denyButton];

        [cell.contentView addSubview:requestsView];
        [cell.contentView addSubview:profileImageView];
        [cell.contentView addSubview:userNameLabel];
    }

    if (indexPath.section == 0)
    {
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
        userNameLabel.frame = CGRectMake(82, 0, 167, 68);
        approveButton.tag = indexPath.row;
        denyButton.tag = indexPath.row;

        NSArray *keys = [self.tableDataSource objectForKey:@"Requests"];
        id aKey = [keys objectAtIndex:indexPath.row];

        userNameLabel.text = [aKey objectForKey:@"User"];

        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[aKey objectForKey:@"URL"]]];
        request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
        request.timeoutInterval = 5.0;

        if ([imageCache objectForKey:aKey])
        {
            profileImageView.image = [imageCache objectForKey:aKey];
        }
        else
        {
            [NSURLConnection sendAsynchronousRequest:request
                                               queue:[NSOperationQueue mainQueue]
                                   completionHandler:
             ^(NSURLResponse *response, NSData *data, NSError *error)
             {
                 if (data)
                 {
                     dispatch_async(dispatch_get_main_queue(), ^
                    {
                        profileImageView.image = [UIImage imageWithData:data];
                        [imageCache setObject:[UIImage imageWithData:data] forKey:aKey];
                    });
                 }
                 else
                 {
                     profileImageView.image = [self profileImage];
                 }
             }];
        }
    }
    else if (indexPath.section == 1)
    {
        requestsView.hidden = YES;
        NSArray *keys = [self.tableDataSource objectForKey:@"Friends"];
        id aKey = [keys objectAtIndex:indexPath.row];
        userNameLabel.text = [aKey objectForKey:@"User"];

        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[aKey objectForKey:@"URL"]]];
        request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
        request.timeoutInterval = 5.0;

        if ([imageCache objectForKey:aKey])
        {
            profileImageView.image = [imageCache objectForKey:aKey];
        }
        else
        {
            [NSURLConnection sendAsynchronousRequest:request
                                               queue:[NSOperationQueue mainQueue]
                                   completionHandler:
             ^(NSURLResponse *response, NSData *data, NSError *error)
             {
                 if (data)
                 {
                     dispatch_async(dispatch_get_main_queue(), ^
                    {
                        profileImageView.image = [UIImage imageWithData:data];
                        [imageCache setObject:[UIImage imageWithData:data] forKey:aKey];
                    });
                 }
                 else
                 {
                     profileImageView.image = [self profileImage];
                 }
             }];
        }
    }
    else if (indexPath.section == 2)
    {
        requestsView.hidden = YES;
        NSArray *keys = [self.tableDataSource objectForKey:@"Blocked"];
        id aKey = [keys objectAtIndex:indexPath.row];
        userNameLabel.text = [aKey objectForKey:@"User"];
    }

    return cell;
}

- (void)displayBlockedUsers
{
    if (self.showingBlockedUsers == YES)
    {
        self.showingBlockedUsers = NO;
        [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:2] withRowAnimation:UITableViewRowAnimationNone];
    }
    else
    {
        self.showingBlockedUsers = YES;
        [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:2] withRowAnimation:UITableViewRowAnimationNone];
    }
}

- (NSInteger)friendRequests
{
    return [[self.tableDataSource objectForKey:@"Requests"]count];
}

- (NSInteger)friends
{
    return [[self.tableDataSource objectForKey:@"Friends"]count];
}

- (NSInteger)blockedUsers
{
    return [[self.tableDataSource objectForKey:@"Blocked"]count];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 2 && self.showingBlockedUsers == NO)
    {
        return 0;
    }
    else
    {
       return 68;
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    if (section == 0)
    {
        if ([self friendRequests] < 1)
        {
            return 0;
        }
        else
        {
            return 22;
        }
    }
    else if (section == 1)
    {
        if ([self friends] < 1)
        {
            return 0;
        }
        else
        {
            return 22;
        }
    }
    else
    {
        if ([self blockedUsers] < 1)
        {
            return 0;
        }
        else
        {
            return 22;
        }
    }
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    switch (section)
    {
        case 0:
        {
            return [self friendRequests];
        }
        case 1:
        {
            return [self friends];
        }
        default:
        {
            return [self blockedUsers];
        }
    }
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    UIView *headerView = [[UIView alloc]init];
    headerView.backgroundColor = [UIColor colorWithRed:248.0/255.0 green:248.0/255.0 blue:248.0/255.0 alpha:1.0];

    UILabel *headerLabel = [[UILabel alloc]initWithFrame:CGRectMake(12, 0, 320, 22)];
    headerLabel.font = [UIFont systemFontOfSize:13.5];
    [headerView addSubview:headerLabel];

    if (section == 0)
    {
        NSInteger friendRequests = [self friendRequests];

        if (friendRequests < 2)
        {
            headerLabel.text = [NSString stringWithFormat:@"%d Request",friendRequests];
        }
        else
        {
            headerLabel.text = [NSString stringWithFormat:@"%d Requests",friendRequests];
        }
    }
    else if (section == 1)
    {
        NSInteger friends = [self friends];

        if (friends < 2)
        {
            headerLabel.text = [NSString stringWithFormat:@"%d Friend",friends];
        }
        else
        {
            headerLabel.text = [NSString stringWithFormat:@"%d Friends",friends];
        }
    }
    else
    {
        UIButton *theButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [theButton addTarget:self action:@selector(displayBlockedUsers) forControlEvents:UIControlEventTouchUpInside];
        theButton.frame = CGRectMake(0, 0, 320, 22);

        [headerView addSubview:theButton];

        NSInteger blocked = [self blockedUsers];

        if (self.showingBlockedUsers == YES)
        {
            headerLabel.text = [NSString stringWithFormat:@"%d Blocked - Tap to Hide",blocked];
        }
        else
        {
            headerLabel.text = [NSString stringWithFormat:@"%d Blocked - Tap to Show",blocked];
        }
    }

    return  headerView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
   return 0;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 3;
}
like image 641
klcjr89 Avatar asked Feb 21 '14 18:02

klcjr89


1 Answers

It looks like you're hiding the section by returning 0 in heightForRowAtIndexPath:. The problem with this approach is that the table view will still create these cells - so in addition to creating the cells for all of the cells actually being displayed, the table view is creating additional cells for all of the blocked users.

To hide a section, just return 0 from tableView:numberOfRowsInSection: when the section is "closed". This way, the table view will not call heightForRowAtIndexPath:, cellForRowAtIndexPath:, or anything else, since you're telling it there are no cells there.

like image 194
Aaron Brager Avatar answered Nov 15 '22 01:11

Aaron Brager