UICollectionview加载来自CloudKit的图像的线程问题

时间:2014-11-11 03:35:00

标签: multithreading ios8 uicollectionview cloudkit

我有一个线程问题,在收集视图中加载图像,其中数据来自cloudkit。我知道这是一个线程/阻塞问题,因为在我实现CK之前,我将一些图像转储到桌面上的文件夹中并从那里读取/解析它们并没有问题。使用CK,我只是通过仪表板创建了一些记录,并且我成功地获得了返回的预期记录,并使用这些结果中的图像填充CV单元格。我将CK查询结果存储在一个数组中,并使用该数组的大小来设置numberOfItemsInSection委托。

这里的问题是...在numberOfItemsInSection委托方法中,我调用了执行CK查询的模型类。由于这显然是一个网络调用,我把它放在后台线程中。从日志记录中,我可以看到查询执行,结果很快就会回来 - 在2-3秒内。但是,CV单元格从不显示,我也看不到自定义单元格已初始化(通过日志记录)。但是,如果我点击相机按钮并拍摄我已经实现的照片,我会将生成的图像添加到数组中,然后在CV上调用reloadData并显示所有单元格(和图像),包括用相机拍摄的新图像。

偶然地,我发现了一个有点工作的hack,它在CV 里面调用reloadData 在numberOfItemsInSection委托方法的后台线程中。结果,我以为我可能在调用reloadData时通过切换回主线程来偶然找到解决方案,但这使得它在一个无限循环中连续调用numberOfItemsInSection方法和cellForItemAtIndexPath并使其落到它滞后的地方到了一点,你几乎无法滚动和点击任何一个单元格都不会做任何事情。

此时,在尝试了许多不同的事情之后,我完全不知道如何解决这个问题。我知道这可能是一个非常简单的解决方案,因为异步加载图像以填充collectionview或tableview非常常见。有人可以提供一些指导吗?在此先感谢!!!

@property (nonatomic) NSInteger numberOfItemsInSection;

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {

    NSLog(@"***numberOfItemsInSection***");

    dispatch_queue_t fetchQ = dispatch_queue_create("load image data", NULL);
    dispatch_async(fetchQ, ^{
        self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
        [self.myCollectionView reloadData]; // should be done on main thread!
    });
    NSLog(@"numberOfItemsInSection: %ld", (long)self.numberOfItemsInSection);

    return self.numberOfItemsInSection;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell"; // string value identifier for cell reuse
    ImageViewCell *cell = [collectionView  dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
    NSLog(@"cellForItemAtIndexPath: section:%ld row:%ld", (long)indexPath.section, (long)indexPath.row);
    cell.layer.borderWidth = 1.0;
    cell.layer.borderColor = [UIColor grayColor].CGColor;
    cell.imageView.contentMode = UIViewContentModeScaleAspectFit;

    ImageData *imageData = [self.imageLoadManager imageDataForCell:indexPath.row]; // maps the model to the UI
    dispatch_async(dispatch_get_main_queue(), ^{
        if (imageData.imageURL.path) {
            cell.imageView.image = [UIImage imageWithContentsOfFile:imageData.imageURL.path];
            [cell setNeedsLayout];
        } else {
            // if imageURL is nil, then image is coming in from the camera as opposed to the cloud
            cell.imageView.image = imageData.image;
            [cell setNeedsLayout];
        }
    }); 

    return cell;
}

1 个答案:

答案 0 :(得分:0)

在返回self.numberOfItemsInSection之前,您应该等到异步调用完成。你可以使用信号量来做到这一点。但那你为什么要做这个异步?你只是得到一个数组的计数。然后你不应该在那里重新加载数据。你在哪里开始你的CloudKit查询?你在onViewDidLoad上做的吗?这也是异步操作。完成后,只需执行collectionView的reloadData。除此之外这样就足够了:

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [self.imageLoadManager.imageDataArray count];
}

如果你真的想在那里使用异步,那么你必须等待结果。您可以将代码更改为:

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    NSLog(@"***numberOfItemsInSection***");

    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    dispatch_queue_t fetchQ = dispatch_queue_create("load image data", NULL);
    dispatch_async(fetchQ, ^{
        self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
        [self.myCollectionView reloadData]; // should be done on main thread!
        dispatch_semaphore_signal(sema);
    });
    NSLog(@"numberOfItemsInSection: %ld", (long)self.numberOfItemsInSection);

    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    return self.numberOfItemsInSection;
}

然后你为什么要去cellForItemAtIndexPath的主队列?它已经在主队列上执行了。