fetchedResultsController.object(at:indexPath)操作阻塞主线程

时间:2018-06-09 02:26:26

标签: ios swift core-data

我正在创建一个在集合视图中显示gif的应用。 我从GIPHY公共API下载JSON,然后从每个单独的GIF URL获取数据,并使用FLAnimatedImage将数据存储在UIImageView中,然后在集合视图中显示它们。 / p>

我已经分配了FLAnimatedImage pod,并添加了NSCoding协议,因此数据可以在Core Data中存档。

在我的collectionView我使用fetchedResultsController获取填充collectionView的对象。

由于分页,每次我在collectionView cellForItemAt(单列单行)中的图像中途停止时,我的API都会调用新图像。

获取新图像的调用是异步的,只要它返回fetchRequest的结果,我就会更新集合视图。这不会阻止主线程。

现在问题在于:我将下载任务中的数据作为核心数据堆栈中的FLAnimatedImage保存后保存每个GIF。现在取消归档它会导致为每个映像创建FLAnimatedImage再次运行,这会导致大量的CPU开销,因为问题是创建FLAnimatedImage非常重CPU,因此将其从堆栈中取消归档。因此,这两个操作都是在分页时对新批量的图像进行的,最终我的应用程序崩溃了。

减速源自每个fetchedResultsController.object(at: indexPath)来电,而我使用的是backgroundContext,所以我知道这不是我所关注的主题。我认为这只是导致崩溃的整体CPU开销。但是,fetchedResultsController是不是应该将实体从堆栈中拉出来?为什么在fetchedResultsController.object(at: indexPath)调用中取消归档并创建属性类型(FLAnimatedImage)?

有没有一种方法可以将FLAnimatedImage存储在堆栈上,每次我使用fetchedResultsController.object(at: indexPath)将其从堆栈中移除时都不需要重新创建它?

你能想出一个可能有帮助的策略吗?

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GSGifCollectionViewCell", for: indexPath) as! GSGifCollectionViewCell

    let gifObject:NSManagedObject = self.fetchedResultsController.object(at: indexPath)

    gifObject.managedObjectContext?.perform {
        let gsGifObject = gifObject as? GSGif
        DispatchQueue.main.async {
            cell.imageView.animatedImage = gsGifObject?.image
            cell.rankLabel.text = "\(gsGifObject?.rank  ?? 0)"
        }
    }


    if let totalObjectsForSectionZero = fetchedResultsController.sections?[0].numberOfObjects {
        self.checkCurrentIndexPathAndGetMoreGifsIfNeccessary(indexPath: indexPath, totalObjects:totalObjectsForSectionZero)
    }


    return cell
}

上面有什么我做错了吗?

这是我的档案代码:

#import "FLAnimatedImage+NSCoding.h"

@implementation FLAnimatedImage (NSCoding)

static NSString *const kFLAnimatedImageCodingData = @"kFLAnimatedImageCodingData";

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    NSData *data = [aDecoder decodeObjectForKey:kFLAnimatedImageCodingData];
    return [self initWithAnimatedGIFData:data];
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.data forKey:kFLAnimatedImageCodingData];
}

@end

这导致主线程被阻塞 - [self initWithAnimatedGIFData:data];

1 个答案:

答案 0 :(得分:0)

首先 - 核心数据不是线程安全的 - 既不用于读取也不用于写入。所以替换

 gifObject.managedObjectContext?.perform {
    let gsGifObject = gifObject as? GSGif
    DispatchQueue.main.async {
        cell.imageView.animatedImage = gsGifObject?.image
        cell.rankLabel.text = "\(gsGifObject?.rank  ?? 0)"
    }
}

简单地说:

 let gsGifObject = gifObject as? GSGif
 cell.imageView.animatedImage = gsGifObject?.image
 cell.rankLabel.text = "\(gsGifObject?.rank  ?? 0)"

你回到后台线程并没有任何帮助,违反了核心数据的多线程规则。

接下来 - 在核心数据中存储大量数据通常不是一个好主意 - 因为在大多数关系数据库中这不是一个好主意。代替 之一:

a)将图像存储在文档目录中,并仅保存核心数据中的文件路径。

b)创建另一个只有数据blob的实体,并从GSGif与它建立关系。这样,只有在访问它时才会加载关系(在核心数据中说“故障”)。