【问题标题】:UITableViewCell's Data is being mixed up when using GCD使用 GCD 时 UITableViewCell 的数据被混淆了
【发布时间】:2013-05-07 21:30:01
【问题描述】:

我正在使用 GCD 在后台线程上加载我的 UITableView 数据,但是这样做会混淆我的自定义 UITableViewCell 中的数据。单元格上的 titleLabel 和 imageView 很好,但每个单元格上的 textLabel(副标题)都是错误的。当数据加载到主线程时不会发生这种情况,并且数据不是来自多个数组,所以我只能猜测是因为我使用了 GCD,我是新手。

首先,我像这样设置 NSOperationQueue...

- (void)setUpTableForAlbums
{
   dispatch_async(dispatch_get_global_queue(0, 0), ^
               {
                   [self setUpTableForAlbumsFD];
                   dispatch_async(dispatch_get_main_queue(), ^
                                  {
                                      [albumTable reloadData];
                                  });
               });
}

setUpTableForAlbumsFD 选择器就是这样...

- (void)setUpTableForAlbumsFD
{
//    __block CLProgressIndeterminateView *clP = [[CLProgressIndeterminateView alloc] initWithFrame:CGRectMake(325, tableScrollView.frame.size.height/2, 310, 20)];
//    [tableScrollView addSubview:clP];
//    [clP startAnimating];
type = @"Albums";
queryAlbums = [MPMediaQuery albumsQuery];
[queryAlbums setGroupingType:MPMediaGroupingAlbum];

mainArrayAlbum = [[NSMutableArray alloc] init];
otherArrayAlbum = [[NSMutableArray alloc] init];
theOtherArrayAlbum = [[NSMutableArray alloc] init];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];

NSArray *fullArray = [queryAlbums collections];
for (MPMediaItemCollection *collection in fullArray)
{
    item = [collection representativeItem];
    NSString *albumName = [item valueForProperty:MPMediaItemPropertyAlbumTitle];
    NSString *albumArtist = [item valueForProperty:MPMediaItemPropertyArtist];

    NSString *filePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", albumName]];
    Album *album = [[Album alloc] init];
    album.albumTitle = albumName;
    album.albumArtwork = [UIImage imageImmediateLoadWithContentsOfFile:filePath];
    if (album.albumTitle.length > 4)
    {
        if ([album.albumTitle hasPrefix:@"The "])
        {
            album.albumOrderTitle = [album.albumTitle substringFromIndex:4];
        }
        else
        {
            album.albumOrderTitle = album.albumTitle;
        }
    }
    else
    {
        album.albumOrderTitle = album.albumTitle;
    }
    album.albumArtist = albumArtist;
    if (![mainArrayAlbum containsObject:album])
    {
        [mainArrayAlbum addObject:album];
    }

}
}

相册自定义类只是数据的容器。

cellForRowAtIndex 路径方法就是这样...

MasterCellAlbum *albumCell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if (!albumCell)
    {
        albumCell = [[MasterCellAlbum alloc] initWithStyle:nil reuseIdentifier:@"Cell"];
    }
    alphabet = [self alphabet:@"album" withIndex:YES];
    [albumCell setSelectionStyle:UITableViewCellEditingStyleNone];
    NSString *alpha = [alphabet objectAtIndex:indexPath.section];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.albumOrderTitle beginswith[c] %@", alpha];
    NSArray *predict = [mainArrayAlbum filteredArrayUsingPredicate:predicate];
    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];


    Album *album1 = [predict objectAtIndex:indexPath.row];
    albumCell.titleLabel.text = album1.albumTitle;
    albumCell.textLabel.text = album1.albumArtist;
    albumCell.avatarImageView.image = album1.albumArtwork;

    longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(albumLittleMenu:)];
    [albumCell addGestureRecognizer:longPress];
    return albumCell;

我是否正确使用了 GCD,或者我应该使用其他方法吗?

【问题讨论】:

    标签: ios objective-c uitableview cocoa-touch uikit


    【解决方案1】:

    哎呀。容我们说,有很多关于这段代码的事情有趣。让我们从第一种方法开始:

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *operation = [NSInvocationOperation alloc];
    
    operation = [operation initWithTarget:self selector:@selector(setUpTableForAlbumsFD) object:nil];
    [operation setCompletionBlock:^
    {
      [albumTable reloadData];
    }];
    [operationQueue addOperation:operation];
    operation = nil;
    

    我认为你要做的是在后台执行 -setUpTableForAlbumsFD 方法,然后在完成后重新加载 tableView。

    首先,completionBlock 不会在主线程上执行(您必须从那里调用-reloadData)。文档说:

    无法保证完成块的确切执行上下文,但通常是辅助线程。因此,您不应使用此块来执行任何需要非常特定的执行上下文的工作。

    执行此方法的更简单方法是:

    dispatch_async(dispatch_get_global_queue(0,0), ^{
      [self setUpTableForAlbumsFD];
      dispatch_async(dispatch_get_main_queue(), ^{
        [albumTable reloadData];
      }
    });
    

    现在是setUpTableForAlbumsFD 方法...

    - (void)setUpTableForAlbumsFD {
        type = @"Albums";
        queryAlbums = [MPMediaQuery albumsQuery];
        [queryAlbums setGroupingType:MPMediaGroupingAlbum];
    
        mainArrayAlbum = [[NSMutableArray alloc] init];
    
        NSArray *fullArray = [queryAlbums collections];
        for (MPMediaItemCollection *collection in fullArray) {
            item = [collection representativeItem];
            NSString *albumName = [item valueForProperty:MPMediaItemPropertyAlbumTitle];
            NSString *albumArtist = [item valueForProperty:MPMediaItemPropertyArtist];
    
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
            NSString *documentsPath = [paths objectAtIndex:0];
    

    为了提高效率,您应该在for 循环之外执行这两行查找NSDocumentDirectory

            NSString *filePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", albumName]];
    
            UIImage *artwork = [UIImage imageImmediateLoadWithContentsOfFile:filePath];
    

    我假设这是一个UIImage 类别方法?

            Album *album = [[Album alloc] init];
            album.albumTitle = albumName;
            if (album.albumTitle.length > 4) {
                if ([[NSString stringWithFormat:@"%c%c%c%c", [album.albumTitle characterAtIndex:0], [album.albumTitle characterAtIndex:1], [album.albumTitle characterAtIndex:2], [album.albumTitle characterAtIndex:3]] isEqual: @"The "]) {
    

    哎呀!就这样吧:if ([album.albumTitle hasPrefix:@"The "]) {

                    album.albumOrderTitle = [album.albumTitle substringWithRange:NSMakeRange(4, album.albumTitle.length-4)];
    

    这里是:album.albumOrderTitle = [album.albumTitle substringFromIndex:4];

                } else {
                    album.albumOrderTitle = album.albumTitle;
                }
            } else {
                album.albumOrderTitle = album.albumTitle;
    

    当您看到多行都在做同样的事情时,这表明您可以将其拉出来并以不同的方式做。例如,您可以总是album.albumOrderTitle 设置为albumTitle,然后仅在专辑标题长度大于4 且前缀为@"The " 时才执行不同的操作。

            }
            album.albumArtist = albumArtist;
            album.albumArtwork = artwork;
            if (![mainArrayAlbum containsObject:album]) {
                [mainArrayAlbum addObject:album];
            }
        }
    }
    

    您的cellForRowAtIndexPath: 也同样令人费解:

    MasterCellAlbum *albumCell = [[MasterCellAlbum alloc] init];
    

    您应该使用UITableView 的单元格重用机制。

    alphabet = [self alphabet:@"album" withIndex:YES];
    [albumCell setSelectionStyle:UITableViewCellEditingStyleNone];
    NSString *alpha = [alphabet objectAtIndex:indexPath.section];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.albumOrderTitle beginswith[c] %@", alpha];
    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
    
    NSArray *predict = [mainArrayAlbum filteredArrayUsingPredicate:predicate];
    

    您为什么每次需要一个单元格时都重新过滤mainArrayAlbum?看起来你总是会得到相同的alphabet,这意味着你总是会定义相同的谓词,这意味着你总是会得到相同的predict数组。

    Album *album1 = [predict objectAtIndex:indexPath.row];
    albumCell.titleLabel.text = album1.albumTitle;
    albumCell.textLabel.text = album1.albumArtist;
    if (album1.albumArtwork) {
        albumCell.avatarImageView.image = album1.albumArtwork;
    } else {
        albumCell.avatarImageView.image = [UIImage imageNamed:@"albumArtInvertedLight1.png"];
    }
    
    longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(albumLittleMenu:)];
    [albumCell addGestureRecognizer:longPress];
    return albumCell;
    

    因此,您的代码可以在一些明显的地方进行改进。老实说,我认为您遇到的问题的答案是因为您试图在后台线程上重新加载 tableview,这是一个 Bad Idea™。

    【讨论】:

    • 谢谢。我是使用 GCD 的新手,还有很多其他的东西。谢谢回答!让知道他们在谈论的人分析我的代码很有用!
    • 我不知道 hasPrefix 方法存在。
    • @Gizmoloon 作为一名优秀的 Cocoa 开发人员,75% 的人都知道框架免费为您做了什么。连我都不认识...
    • 抱歉,我实际上并没有在后台线程上加载它。我一直在直接调用第二种方法,现在我仍然遇到问题。即使上面提出的所有建议都已排序,它仍然混淆了单元格上的标签......
    • 我觉得自己像个白痴。我在整个 ViewController 中使用“项目”iVar。我改了名字,很好。对此感到抱歉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-14
    • 2020-11-17
    • 2012-04-01
    • 1970-01-01
    • 2013-02-02
    • 1970-01-01
    相关资源
    最近更新 更多