使用大型数据库时,如何避免 UITableView 中的自定义单元格加载时间过长

Posted

技术标签:

【中文标题】使用大型数据库时,如何避免 UITableView 中的自定义单元格加载时间过长【英文标题】:How do I avoid long loading time with custom cell in a UITableView when using a big database 【发布时间】:2015-10-06 19:14:18 【问题描述】:

在我正在开发的应用程序中,我必须从一个相当大的数据库中获取和使用数据(文本和图像),并且当 加载 tableview 时,应用程序会冻结 2-5 秒。这可能会破坏 UX,因为 UI 感觉对操作反应不快。

我认为问题是存在许多图像(不是很重,50x50px),每个自定义 cel 都有一个,大约有1000+ 行。 我需要知道最好的方法避免这个加载时间长,或者如何隐藏它。

用户点击一个按钮打开tableview,该按钮执行一个segue,然后我在viewDidLoad() 中获取数据。正如我所知道的,viewDidLoad() 方法可以阻止交互直到完成。在 viewDidLoad() 内部,我调用了一个名为 fetchCard() 的函数。它将Card 对象放入一个数组中,该对象包含我在单元格中使用的属性(名称、缩略图)。

func fetchCard() 

    cards = []

    let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "Card")
    let sorter: NSSortDescriptor = NSSortDescriptor(key: sorterParameter , ascending: true)
    fetchRequest.predicate = definitivePredicate
    fetchRequest.sortDescriptors = [sorter]
    fetchRequest.returnsObjectsAsFaults = true

    do 
        let results = try moc.executeFetchRequest(fetchRequest)
        cards = results as! [Card]
     catch 
        print("Fetch failed: \(error)")
    

有没有办法加载,例如,只加载可见行,这行得通吗/这样做是个好习惯吗?有更好的方法吗?

P.S.:为了避免基于个人喜好的讨论,“更好”是指加载速度方面性能最高的方式。

编辑:添加代码示例。

【问题讨论】:

您是否使用 CoreData 进行本地存储?你能提供一些如何获取数据的示例代码吗? google 异步编码 @haluzak 是的,我正在使用核心数据,我将编辑我的问题。 你给表添加索引了吗?什么是大,1K,10K,100k?是所有图像都不同还是许多相同? 我获取了大约 1100 个对象。每个单元格的图像都是唯一的。 【参考方案1】:

问题是您在 viewDidLoad() 方法中加载了许多数据,并且您没有在后台线程上异步加载它们。当您在 viewDidLoad() 中调用 fetchCard() 时,您会阻止加载整个屏幕,直到从 DB 加载完成。

要在后台线程上加载数据,您必须这样做:

let queue: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
    dispatch_async(queue,  () -> Void in
        let context: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
        context.parentContext = moc

        let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "Card")
        let sorter: NSSortDescriptor = NSSortDescriptor(key: sorterParameter , ascending: true)
        fetchRequest.predicate = definitivePredicate
        fetchRequest.sortDescriptors = [sorter]
        fetchRequest.returnsObjectsAsFaults = true

        do 
            let results = try context.executeFetchRequest(fetchRequest)
            cards = results as! [Card]
         catch 
            print("Fetch failed: \(error)")
        

        dispatch_async(dispatch_get_main_queue(),  () -> Void in
            tableView.reloadData()
        )
    )

此外,如果从 CoreData 加载变慢,您将不得不找出原因。为您排序/过滤的对象中的变量添加索引可能会有所帮助。在sortDescriptors中添加1个描述符,尝试索引描述符中使用的sorterParameter

如果您将图像存储在 CoreData 数据库中,最好也异步加载它们以保持 tableview 的滚动流畅。您可以使用与以前类似的代码,只需将图像加载移动到 GCD 块内并将图像设置为主线程中的图像视图('dispatch_async(dispatch_get_main_queue()...')。

【讨论】:

我试过你的代码,但是从 Core Data 加载数据仍然需要太多时间,因为我看到 tableview 是空的,过了一会儿它崩溃了。我会寻找索引和数据库:你能给我指一个关于索引的参考吗? 我也会让图片异步加载 @Qubex_ 考虑 OP 的声明:“从核心数据加载数据需要太多时间”。这真的是一个异步问题吗?并不是不应该使用异步,只是它不是当前的性能问题。 @zaph 正如你所建议的,我必须使用索引。我不知道他们,我现在正在阅读您链接的另一个答案。 @zaph 正如我所提到的,为适当的变量添加索引以加快数据库中的整个搜索速度肯定会有所帮助,我会尝试在他提供的聊天中帮助 Qubex【参考方案2】:

根据 OP 所述,问题解决方案是在疮正在使用的属性(列)上创建索引。

有关更多信息,请参阅此SO Question。然后跟进 Core Data 文档或一本好的 Core Data 书籍。

注意事项: 1. 如果表行数大约为 1000,因为 OP 暗示使用索引核心数据的访问时间应该几乎是瞬时的。

    重新生成 50 x 50 像素的图像的时间也应该是微不足道的。

    在性能问题上,重要的是要衡量性能问题在哪里,否则时间和精力会花在错误的领域上。

【讨论】:

【参考方案3】:

UITableView 已经只加载cellForRowAtIndexPath 中的可见单元格。假设您正在重用单元格,您的问题很可能在于加载数据,而不是显示数据。

看看苹果的异步加载示例项目:LazyTableImages

调整大图像的大小以适应较小的表格视图单元格大小的图像确实需要一些处理能力和相当数量的 IO。如果可能,您应该为表格视图使用较小的缩略图,而不是大图像。不过,在较新的设备上可能需要较大尺寸的图像才能注意到这一点。

【讨论】:

感谢您的链接,我将尝试实现该链接 @Qubex_ Core Data 访问应该很快,首先你需要找出什么是慢的:成像的 Core Data。您需要衡量并了解时间。 @Marcus 图片是 50 x 50,所以不是很大。

以上是关于使用大型数据库时,如何避免 UITableView 中的自定义单元格加载时间过长的主要内容,如果未能解决你的问题,请参考以下文章

避免重新加载 UITableView 数据

避免在 ios 中的 UITableView 中出现重复条目

iOS:UIImageView随着UITableView.rowHeight的变化而变化,如何避免?

使用大型数据结构时,避免 Java(eclipse) 中的“内存不足错误”?

UITableViewCell 在大型 UITableView (Xcode, iOS) 中不可见时为 nil

带有 UISearchBar 和 UITableView 的 UISearchDisplayController - 如何避免“实时结果”?