在获取照片时使用GCD的多个线程

时间:2013-05-02 05:20:03

标签: iphone ios multithreading asynchronous grand-central-dispatch

我必须从地址簿中提取联系人,并在UITableView找到照片旁边的照片。

我使用ABContactsHelper库获取所有联系人,然后使用GCD块异步获取UITableView中可见行的照片。

我提到了Apple示例代码,它等待UITableView完成滚动,得到Visible NSIndexPath s&创建了获取照片的线程。到目前为止,我的问题有两个。

首先,如果用户滚动,停止,滚动&停止并且做了很多次,生成的线程太多,无法获取使应用程序变慢的照片。

其次,当线程返回设置缓存中的照片以及UITableViewCell时,UIImageView的引用现在正被重用于UITableViewCell中的另一条记录,因此照片当特定记录的线程返回时,它被置于错误的记录上,最终被正确的记录替换。

以下是cellForRowAtIndexPath以及UITableView停止滚动时使用的代码。

- (void)loadImagesLazilyForIndexPath:(NSIndexPath *)indexPath photo:(UIImageView *)photo contact:(ContactModel *)contact
{

    if (!self.tableView.isDragging && !self.tableView.isDecelerating) {
        UIImage *thePhoto = [self.imagesForContacts objectForKey:indexPath];
        if (!thePhoto) {
            // NSLog(@"Photo Not Found - Now Fetching %@", indexPath);
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
                @autoreleasepool {
                    UIImage *image = [[JMContactsManager sharedContactsManager] photoForContact:contact];
                    if (!image)
                        image = self.noProfilePhoto;
                    [self.imagesForContacts setObject:image forKey:indexPath];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        // NSLog(@"Photo Fetched %@", indexPath);
                        @autoreleasepool {
                            NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
                            BOOL visible = [visiblePaths indexOfObjectPassingTest:^BOOL(NSIndexPath * ip, NSUInteger idx, BOOL *stop) {
                                if (ip.row == indexPath.row && ip.section == indexPath.section) {
                                    *stop = YES;
                                    return 1;
                                }
                                return 0;
                            }];
                            if (visible)
                                photo.image = [self.imagesForContacts objectForKey:indexPath];
                            [[NSURLCache sharedURLCache] removeAllCachedResponses];
                        }
                    });
                }
            });
        } else {
            // NSLog(@"Photo Was Found %@", indexPath);
            @autoreleasepool {
                photo.image = [self.imagesForContacts objectForKey:indexPath];
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

对于这种功能,我会使用NSOperation和NSOperationQueue,它们构建在GCD之上,但它为您提供了取消操作的机会。您可以检查哪些操作不再可见并取消它们。这样你可以控制参考“离开” 我还看到另一个可能导致“问题”的问题,似乎你在NSMutableDictionary中缓存图像,不是吗?或者您使用的是NSCache吗?如果它是一个NScache很好,但大多数可变对象都不是“自然”线程安全的 提升队列的优先级: - )

答案 1 :(得分:1)

如@Andrea所述,您应该使用NSOperationQueue,它可以让您取消排队的任务。 通过indexPath将图像缓存索引到表中并不健壮,因为给定元素的索引路径可能会发生变化(尽管可能不是在您的特定情况下)。您可以考虑通过ABRecord.uniqueId索引图像缓存。

在任何情况下,它都无法解决您的图像被设置为同一个单元格两次或更多的问题。发生这种情况是因为UITableView没有为每个项目分配视图,而是管理UITableCellViews池,每次都重复使用它。你可以做的是以下几点:

// Assuming your "ContactCellView" inherits from UITableCellView and has a contact property
// declared as follows: @property (retain) ABRecord *contact.

- (void) setContact:(ABRecord*)contact
{
    _contact = contact;
    __block UIImage *thePhoto = [self.imagesForContacts objectForKey:contact.uniqueId];
    if (thePhoto == nil) {
        _loadImageOp = [NSBlockOperation blockOperationWithBlock:^(void) {

            // Keep a local reference to the contact because it might change on us at any time.
            ABRecord *fetchContact = contact;

            // Fetch the photo as you normally would
            thePhoto = [[JMContactsManager sharedContactsManager] photoForContact:fetchContact];
            if (thePhoto == nil)
                thePhoto = self.noProfilePhoto;

            // Only assign the photo if the contact has not changed in the mean time.
            if (fetchContact == _contact)
                _contactPhotoView.image = thePhoto;
        }];        
    } else {
       _contactPhotoView.image = thePhoto;
    }
}