【问题标题】:Threading issue with UICollectionview loading images from CloudKitUICollectionview 从 CloudKit 加载图像的线程问题
【发布时间】:2014-11-11 22:11:02
【问题描述】:

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

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

偶然地,我发现了一个有点工作的技巧,它在 CV inside numberOfItemsInSection 委托方法的后台线程上调用 reloadData。结果,我想我可能通过在调用 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;
}

【问题讨论】:

  • 不要猜测。 找出问题的原因,找出问题所在,然后编写解决方案来解决该特定问题。线程可能难以推理;确保您精通编码技术,以便安全地使用它们。

标签: multithreading ios8 uicollectionview cloudkit


【解决方案1】:

在返回 self.numberOfItemsInSection 之前,您应该等到异步调用完成。您可以使用信号量来做到这一点。但是那你为什么要做这个异步呢?你只是得到一个数组的计数。然后你不应该在那里 reloadData 。你从哪里开始你的 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中的主队列呢?它已经在主队列上执行了。

【讨论】:

  • 感谢您的回复。我正在执行此异步操作,因为以下行是模型中 CK 查询的开始...ImageLoadManager 是模型类。 self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray 计数];
  • 好的,但是 CloudKit 查询本身已经是异步的。你不必包装它。然后,您必须确保在进行计数之前 CloudKit 查询已准备就绪。您可以为此使用信号量结构。
  • 是的,那是我的问题的一部分......我现在不知道该怎么做,因为我正在远程获取数据。我很确定这是错误的,但不确定这样做的“正确方法”是什么。我从未听说过信号量,但我刚刚尝试了您的代码,但在 var sema 行上出现错误 - 使用未声明的标识符 - var。你在使用 Swift 语法吗?我将不得不谷歌信号量。非常感谢您的帮助!
  • 我用谷歌搜索了信号量,并且能够让您的代码正常工作,但结果完全没有区别......除非我拍照,否则 CV 不会呈现,这最终会触发要呈现的 CV 自定义单元格代码。唯一可行的方法是使用上面的原始代码,我对模型进行异步调用并加载 CK 数据并在后台线程中的 CV 上调用 reloadData,我知道这是不正确的。
  • 有趣的是,我将 Swift 代码插入到您的目标 C 示例中。现在将为您更新它...
猜你喜欢
  • 2014-06-11
  • 2020-08-24
  • 2018-12-09
  • 1970-01-01
  • 1970-01-01
  • 2016-10-13
  • 1970-01-01
  • 2011-02-10
  • 1970-01-01
相关资源
最近更新 更多