iOS:如何调试UI卡顿 [iPad2双核]

5
在我的应用程序中,我需要处理来自网络的大量数据,并将其解析为优化的本地格式,然后将它们保存到数据库中,或者如果任何用户界面正在等待该数据,将它们发送到用户界面。我知道在主线程中进行重度解析工作很愚蠢,因为它会阻塞主线程并使用户界面交互非常卡顿。所以这是我尝试过的确保用户界面线程空闲的方法:
  • Global optimization to remove really slow code despite what thread it is in.
  • Lower all thread priorities, ie,

    NSOperation.threadPriority=0.1;
    NSThread.threadPriority=0.1;
    dispatch_async(dispatch_get_global_queue(PRIORITY_LOW));
    
在这两个步骤之后,当在后台线程中执行一些重活时,UI更加流畅了,但仍然有些卡顿。
问题看起来像这样: 当我在表格上进行平滑滚动时,突然间表格会冻结约0.1秒,然后就像平常一样平滑流畅。 在冻结期间,我可以在Instruments中看到CPU爆发。但在单独的线程视图中,我可以看到主线程OK,几乎没有CPU消耗。
那么为什么UI表格仍然会冻结呢? 后台线程为什么仍然会以如此低的线程优先级占用如此多的CPU周期?
你们是如何确保UI始终响应的? 是否有任何有用的工具来突出显示导致UI冻结的地方?

最好的方法是像 Twitter 应用程序一样做。有一些实现可以帮助你完成这个功能。[http://blog.leahculver.com/2010/07/iphone-pull-to-refresh.html] - JOA80
1
@Ashishail 感谢您的评论。尽管下拉刷新是减少流量的好方法,但对于我的应用程序来说并不适用,因为它需要离线工作。 - Jay Zhao
1
哦,我以为你说你从网络获取,对于本地数据,有很多事情可以做。检查一下你的cellforrowatindexpath是否尽可能快地返回。如果记录数在几百条以下,可以使用预填充数组。如果记录数在千位数以上,则可以根据最后一行调用进行预取。例如,首先获取100行,在调用第50行时再获取100行,依此类推。 - JOA80
@Ashishail 谢谢!它有效了!我学会了一种技巧,可以在主线程中移除所有潜在的同步调用(如dispatch_sync),特别是对于scrollViewDidScroll和cellForRowAtIndexPath。结果是滚动比以前更加流畅! - Jay Zhao
1个回答

3
一个好的解决方案是使用Grand Central Dispatch (GCD)和Objective-C blocks与本地缓存结合使用。当你的表格单元加载时,应该尽快从tableView:cellForRowAtIndexPath:返回,并使用任何已经保存在本地的数据设置好表格单元。任何需要下载数据或进行任何重量级处理的单元部分应该有一个占位符(例如一个活动指示器)。重量级处理或下载将在另一个线程上进行:
// ...This code is at the end of tableView:cellForRowAtIndexPath: where cell
// is already set up and you should have access to your cache and your data
// manager object that will do the processing or downloading for you.

// Show the placeholder (it will hide when real data is set)
cell.placeholder.hidden = NO;
// Check if item is local
MyDataItem* item = [myCache objectForKey:itemKey];
if(item != nil)
{
    // Item is available locally, set up the cell now
    cell.someImageView.image = item.image;
    cell.someLabel.text = item.text;
    cell.placeholder.hidden = YES;
}
else
{
    // Item is not in cache, go and get it ready
    [myDataManager goAndGet:itemKey completion:^(MyDataItem* theItem)
    {
        // Put the item in the cache
        [myCache setObject:theItem forKey:itemKey];
        // This block will be executed later and the cell may have been reused
        // by then, so we need to ask the table view directly for the cell.
        // This returns nil if cell is not currently visible.
        MyTableCell* theCell = (MyTableCell*)[tableView cellForRowAtIndexPath:indexPath];
        // Set up the table cell
        if(theCell != nil)
        {
            myCell.someImageView.image = theItem.image;
            myCell.someLabel.text = theItem.text;
            myCell.placeholder.hidden = YES;
        }
    }];
}
return cell;

从上面的代码可以看出,即使需要进行一些繁重的处理,单元格也能够非常快速地返回。这使得UI保持流畅响应,而不会因为表视图上的卡顿滚动而产生任何延迟。数据管理器将使用GCD来执行处理,类似于以下内容:
- (void)goAndGet:(NSString*)itemKey completion:(void (^)(MyDataItem* theItem))completionBlock
{
    // Note: this uses DISPATCH_QUEUE_PRIORITY_DEFAULT - but you might want to use
    // a different queue, probably one you created yourself for these tasks.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
    {
        // Do heavy processing or downloading here
        MyDataItem* item = [heavyProcessor doProcessingForItem:itemKey];

        // The completion block must happen on the main queue
        dispatch_async(dispatch_get_main_queue(), ^
        {
            // Call the completion block
            completionBlock(item);
        });
    });
}

请注意,上面的所有代码都假定您正在使用 ARC :)

谢谢!很抱歉我这么晚才查看答案。是的,最终我使用了与你类似的解决方案。我实现了后台渲染,现在滚动非常流畅 :) - Jay Zhao
1
没问题 - 希望我的回答对您有用。即使您已经使用不同的方式使其工作,我真的建议阅读关于块和GCD的内容(请参见本答案顶部的链接)。GCD使得执行需要在另一个线程上运行但需要在主线程上回调的后台任务非常容易,并且块确实有助于暂时保留对稍后需要操作的对象的引用。看看上面的代码多简单 :) - jhabbott
是的,我使用GCD实现了后台渲染,它非常方便使用,我几乎得到了与你的答案相同的模式,谢谢你的有用回答! - Jay Zhao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接