5
votes

My app is want to get the albums list of the iphone and all the photos in certain album.

In the app I enumerate the photos in one album of the iphone. As there may be lots of photos of certain album, considering of performance I use GCD:dispatch_async. But it always crashes when the tableview cell updates which is invoked by KVO. I've no idea about whether I use KVO or GCD in a wrong way.

Now alternatively I use performSelectorInBackground: replacing of dispatch_async. Now the app is not crashed but the app's performance is poor: the title of the cell will only be shown when you touch on it or scroll the tableview when there are many photos. In other words, the main thread must be blocked.

Attached is the code and the core code is in AlbumListViewController.m.

Can any one help me to check it ?

I just want to know: 1 why the app is crashed if using dispatch_async 2 how can I improve the performance in case of many photos.

thanks.

Below is my Code:

//
//  RootViewController.h
//  AlbumDemo


#import 

@interface RootViewController : UITableViewController {
    NSMutableArray *_listArray;
}

@property (nonatomic, retain) NSMutableArray *listArray;

@end


//  RootViewController.m


#import "RootViewController.h"
#import 
#import "AlbumListViewController.h"
NSString *thumnail   = @"thumnail";
NSString *albumName  = @"albumName";
NSString *albumNum   = @"albumNum";
NSString *albumGroup = @"albumGroup";
@implementation RootViewController
@synthesize listArray = _listArray;

#pragma -
#pragma Function
- (void)setUp
{
    _listArray = [[NSMutableArray alloc] initWithCapacity:1];
    self.title = @"Albums";
}
- (void)fetchAlbumList
{
    ALAssetsLibrary *assetLib = [[[ALAssetsLibrary alloc] init] autorelease];
    ALAssetsFilter *fileter = [ALAssetsFilter allPhotos];
    [assetLib enumerateGroupsWithTypes:ALAssetsGroupAll 
                            usingBlock:^(ALAssetsGroup *group, BOOL *stop)
     {
         if (group)
         {
             [group setAssetsFilter:fileter];
             NSString *_groupName = [group valueForProperty:ALAssetsGroupPropertyName];
             NSNumber *_groupNum = [NSNumber numberWithInteger:[group numberOfAssets]];
             UIImage *_groupImage = [UIImage imageWithCGImage:[group posterImage]];

             NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:_groupName,albumName,_groupNum,albumNum,_groupImage,thumnail,group,albumGroup, nil];

             [_listArray addObject:dic];
             [self.tableView reloadData];

         }
         else
         {
             NSLog(@"_listArray :%@",_listArray);
         }

     } 
                          failureBlock:^(NSError *error) 
     {
         NSLog(@"Error: %@", error);;
     }
     ];

}
#pragma -
#pragma ViewController lift cycle
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self setUp];
    [self fetchAlbumList];

}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return  50;
}
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [_listArray count];
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UILabel *nameLab = nil;
    UILabel *numLab = nil;
    UIImageView *thumnailImage = nil;

    UIFont *font = [UIFont boldSystemFontOfSize:18];

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
         cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

        thumnailImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0,50, 50)];
        thumnailImage.tag = 100;
        [cell.contentView addSubview:thumnailImage];
        [thumnailImage release];


        nameLab = [[UILabel alloc] initWithFrame:CGRectMake(60, 10, 100, 30)];
        nameLab.tag = 200;
        nameLab.backgroundColor = [UIColor clearColor];
        nameLab.font = font;
        [cell.contentView addSubview:nameLab];
        [nameLab release];

        numLab = [[UILabel alloc] initWithFrame:CGRectMake(200, 10, 50, 30)];
        numLab.tag = 300;
        numLab.backgroundColor = [UIColor clearColor];
        numLab.textColor = [UIColor grayColor];
        numLab.font = font;
        [cell.contentView addSubview:numLab];
        [numLab release];
    }
    else
    {
        thumnailImage = (UIImageView *)[cell.contentView viewWithTag:100];
        nameLab = (UILabel *)[cell.contentView viewWithTag:200];
        numLab = (UILabel *)[cell.contentView viewWithTag:300];
    }

    NSDictionary *dic = [self.listArray objectAtIndex:indexPath.row];

    thumnailImage.image = (UIImage *)[dic valueForKey:thumnail];

    NSString *title = [dic valueForKey:albumName];
    CGSize titleSize = [title sizeWithFont:font];
    CGRect rect = nameLab.frame;
    rect.size = titleSize;
    nameLab.frame = rect;
    nameLab.text = title;

    rect = numLab.frame;
    rect.origin.x = 60 + nameLab.frame.size.width + 10;
    numLab.frame = rect;

    numLab.text = [NSString stringWithFormat:@"(%d)",[[dic valueForKey:albumNum] intValue]];


    // Configure the cell.
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary *dic = [self.listArray objectAtIndex:indexPath.row];

    AlbumListViewController *viewController = [[AlbumListViewController alloc] initWithAssetGroup:[dic valueForKey:albumGroup]];
    [self.navigationController pushViewController:viewController animated:YES];
    [viewController release];
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Relinquish ownership any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload
{
    [super viewDidUnload];

    // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
    // For example: self.myOutlet = nil;
}

- (void)dealloc
{
    My_Release (_listArray);
    [super dealloc];
}

@end


//  AlbumListViewController.h
//  AlbumDemo

#import 
#import 

@interface AlbumListViewController : UITableViewController {
    NSMutableArray *_marr;
    ALAssetsGroup *_assetsGroup;
}

@property (nonatomic, retain) NSMutableArray *list;
@property (nonatomic, retain) ALAssetsGroup *assetsGroup;

- (id)initWithAssetGroup:(ALAssetsGroup *)group;
@end

//  AlbumListViewController.m
//  AlbumDemo

#import "AlbumListViewController.h"

@interface PhotoObj : NSObject {
    NSString *_name;
    UIImage *_thumbnail;
    UIImage *_fullImage;
}

@property (nonatomic, copy  ) NSString *name;
@property (nonatomic, retain) UIImage *thumbnail;
@property (nonatomic, retain) UIImage *fullImage;
@end

@implementation PhotoObj
@synthesize name = _name;
@synthesize thumbnail = _thumbnail,fullImage = _fullImage;
- (void)dealloc
{
    My_Release(_thumbnail);
    My_Release(_fullImage);
    My_Release(_name);
    [super dealloc];
}
@end

@interface AlbumListViewController()

- (NSMutableArray*)list;
- (NSUInteger)countOfList;
- (id)objectInListAtIndex:(NSUInteger)idx;
- (void)insertObject:(id)anObject inListAtIndex:(NSUInteger)idx;
- (id)objectInListAtIndex:(NSUInteger)idx;
- (void)removeObjectFromListAtIndex:(NSUInteger)idx;
- (void)replaceObjectInListAtIndex:(NSUInteger)idx withObject:(id)anObject;
- (void)setList:(NSMutableArray *)_arr;

@end

@implementation AlbumListViewController
@synthesize  assetsGroup = _assetsGroup;

- (id)initWithAssetGroup:(ALAssetsGroup *)group
{
    self = [self initWithStyle:UITableViewStylePlain];
    if (self )
    {
        _marr = [[NSMutableArray alloc] initWithCapacity:1];
        self.assetsGroup = group;
        self.tableView.delegate = self;
        self.tableView.dataSource = self;

    }
    return  self;
}

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)dealloc
{
    My_Release(_marr);
    My_Release(_assetsGroup);
    [self removeObserver:self forKeyPath:@"list"];
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

}

#pragma mark - View lifecycle
- (void)parseAssetGroup
{
    [_marr removeAllObjects];
    [self.assetsGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
        if (result)
        {
            PhotoObj *obj = [[PhotoObj alloc] init];
            obj.thumbnail = [UIImage imageWithCGImage:[result thumbnail]];
            ALAssetRepresentation *represention = [result defaultRepresentation];
            obj.fullImage = [UIImage imageWithCGImage:[represention fullScreenImage]];
            obj.name = [[represention url] absoluteString];


            [self willChangeValueForKey:@"list"];
            [self insertObject:obj inListAtIndex:[_marr count]];
            [self didChangeValueForKey:@"list"];
            My_Release(obj);
        }

    }];

}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self addObserver:self forKeyPath:@"list" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:NULL];
    /*
     if performSelectorInBackground, the perofrmance is poor
     as the title of the cell will be shown in a long time and it now seems the main thread is blocked
     */
    [self performSelectorInBackground:@selector(parseAssetGroup) withObject:nil];
    /*
     using dispatch_async it always crashes 
     as it says the sth is wrong with the tableview update

     */

//  dispatch_async(dispatch_get_main_queue(), ^{
//      [self parseAssetGroup];
//  });
}

- (void)viewDidUnload
{
    [super viewDidUnload];

}

#pragma mark - Table view data source
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return  50;
}
- (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 [_marr count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UIImageView *thumbNail = nil;
    UILabel *nameLab = nil;
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

        thumbNail = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        thumbNail.tag = 99;
        [cell.contentView addSubview:thumbNail];
        [thumbNail release];

        nameLab = [[UILabel alloc] initWithFrame:CGRectMake(60, 10, 240, 40)];
        nameLab.numberOfLines = 2;
        nameLab.font = [UIFont systemFontOfSize:16];
        nameLab.tag = 199;
        [cell.contentView addSubview:nameLab];
        [nameLab release];
    }
    else
    {
        thumbNail = (UIImageView *)[cell.contentView viewWithTag:99];
        nameLab = (UILabel *)[cell.contentView viewWithTag:199];
    }
    // Configure the cell...
    PhotoObj *obj = [_marr objectAtIndex:indexPath.row];
    nameLab.text = obj.name;
    thumbNail.image = obj.thumbnail;

    return cell;
}
#pragma mark -
- (NSUInteger)countOfList
{
    return [_marr count];
}
- (NSMutableArray*)list
{
    return _marr;
}
- (void)setList:(NSMutableArray *)_arr
{
    if (_marr != _arr)
    {
        [_marr release];
        _marr = _arr;
    } 
}

- (id)objectInListAtIndex:(NSUInteger)idx
{
    return [_marr objectAtIndex:idx];
}

- (void)insertObject:(id)anObject inListAtIndex:(NSUInteger)idx
{
    if ([NSThread isMainThread])
    {
        NSLog(@"insert main thread");
    }
    else
    {
        NSLog(@"insert not main thread");
    }
    [_marr insertObject:anObject atIndex:idx];
}


- (void)removeObjectFromListAtIndex:(NSUInteger)idx
{
    [_marr removeObjectAtIndex:idx];
}
- (void)replaceObjectInListAtIndex:(NSUInteger)idx withObject:(id)anObject
{
    [_marr replaceObjectAtIndex:idx withObject:anObject];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    NSIndexSet *indices = [change objectForKey:NSKeyValueChangeIndexesKey];
    if (indices == nil)
        return; // Nothing to do

    // Build index paths from index sets
    NSUInteger indexCount = [indices count];
    NSUInteger buffer[indexCount];
    [indices getIndexes:buffer maxCount:indexCount inIndexRange:nil];

    NSMutableArray *indexPathArray = [NSMutableArray array];
    for (int i = 0; i 
1

1 Answers

7
votes

I ran into exactly the same problem today. In short, the reason is you cannot do UIKit related tasks, like updating a table, or in my case a Textview, from the background dispatch queue. Check the link below for more details.

comparison GCD vs. performSelectorInBackground: dispatch_async not in background

A possible solution is the following: instead of assigning your fresh data in your update block directly to the KVO variable which causes the crash, you dispatch another block which does this to the main queue, from inside your update block. If you use the dispatch_async_f function to do this, you can pass a pointer to your data as context.

Like this:

dispatch_async(yourQueue, ^() {
  NSArray *data;
  // do stuff to alloc and fill the array
  // ...
  dispatch_async(dispatch_get_main_queue(), ^() {
    myObj.data = data; // the assignment, which triggers the KVO.
  });
});

For me this works without retaining and releasing the data. Not sure, if this correct.