我有一个UITableViewCell
,里面有一个水平的UIStackView
。在UIViews
中创建并将其添加到堆栈视图中会导致一些性能问题。表格视图的滚动中有一个明确的 blip 。我已经在仪器中对其进行了分析,并且调用更新堆栈视图是整个应用程序中第二大的任务。
设置如下。我正在使用MVP,并且在演示者中使用该单元格,在设置单元格时调用了updateStackView(with items: [Any])
,并且在cellForRowAtIndexPath
方法中设置了该单元格。
我的原始updateStackView(with items: [Any])
如下:
func updateStackView(_ items: [Any]) {
items.forEach { item in
if let customViewModel = item as? CustomViewModel {
myStackView.addArrangedSubview(CustomView(customViewModel))
} else if item is Spacing {
myStackView.addArrangedSubview(PipeSpacerView())
}
}
}
所以这是瓶颈,我决定按以下步骤将某些内容移到后台:
func updateStackView(_ items: [Any]) {
DispatchQueue.global(qos: .background).async {
items.forEach { item in
if let customViewModel = item as? CustomViewModel {
DispatchQueue.main.async {
self.myStackView.addArrangedSubview(CustomView(customViewModel))
}
} else if item is Spacing {
DispatchQueue.main.async {
self.myStackView.addArrangedSubview(PipeSpacerView())
}
}
}
}
}
Kinda丑陋,因为它具有一些深层嵌套,但对性能产生了很大影响。
我对此感到满意,直到我向下滚动到表格视图的底部并开始向上滚动。我注意到当单元格滚动到视图中时,某些CustomViews
在堆栈视图中两次。单元格完全可见后,堆栈视图会在大多数情况下将其自身更正为。
在我的prepareForReuse
中,我确实对以下函数进行了调用:
func resetDescriptionStackView() {
descriptionStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
}
一直都可以正常工作的地方是全部在主线程上。
任何帮助,我们将不胜感激。我可以肯定这是一个线程问题,因为当我将内容移至后台时才开始抬起头来。
答案 0 :(得分:0)
您遇到的问题是由于表视图对单元格的重用。
发生的事情是,当您向下滚动时,当前可见的单元格已经运行了其后台任务来创建视图。当它们完成时,当前可见的单元格已更改为显示当前数据,但它们仍然是表视图之前使用的相同单元格实例。因此,现在两个闭包会将视图添加到同一堆栈视图实例。 (在单元被重用之前一个关闭,在单元被重用之后另一个关闭)
您可以做的是为每个单元创建一个UUID,将其存储在单元中,在后台任务关闭中捕获它,然后测试是否相等。如果不是这样,则该任务的结果无关紧要,因为单元格已更改为可以显示另一个可见行。
例如:
class CustomViewCell: UITableViewCell
{
private var cellId: String = ""
// call this method for each cell in cellForItemAt implementation
func configure(_ items: [Any])
{
cellId = UUID() // generates a randomized string
updateStackView(items)
}
private func updateStackView(_ items: [Any]) {
let curretCellId = cellId
DispatchQueue.global(qos: .background).async {
items.forEach { item in
if let customViewModel = item as? CustomViewModel {
DispatchQueue.main.async {
if (self.cellId == currentCellId)
{
self.myStackView.addArrangedSubview(CustomView(customViewModel))
}
}
} else if item is Spacing {
DispatchQueue.main.async {
if (self.cellId == currentCellId)
{
self.myStackView.addArrangedSubview(PipeSpacerView())
}
}
}
}
}
}
}
答案 1 :(得分:0)
首先,.background服务质量主要用于大型/艰苦的后台工作(例如,发送统计数据,与服务器同步数据库等),但是当您想要在与主服务器不同的队列上进行一些快速工作时然后使用.userInitiated或.utility正确更新UI。另外,我建议您将自己的OperationQueue与后台Qos之一配合使用。
为了使代码更好,您可以使用“ defer”语句(documentation)并将代码分隔为小函数。
如果您想一次添加子视图,则可以为CustomView和Spacing添加特殊ID,然后进行检查。 完整代码:
//For each our CustomView and Spacing we set uniq id to tag property in init of each class.
/// Operation queue that work with our UIStackView
fileprivate lazy var stackViewOperationQueue: OperationQueue = {
let queue = OperationQueue()
queue.name = "StackView OperationQueue"
queue.qualityOfService = .userInitiated
return queue
}()
func updateStackView(_ items: [Any]) {
stackViewOperationQueue.addOperation { [weak self] in
items.forEach { self?.addArrangedSubviewToStackView(by: $0) }
}
}
fileprivate func addArrangedSubviewToStackView(by item: Any) {
var handler: () -> () = {}
defer {
DispatchQueue.main.async {
handler()
}
}
if let customViewModel = item as? CustomViewModel {
handler = { [weak self] in
let viewToAdd = CustomView(customViewModel)
self?.checkAndAddSubviewIfNeeded(viewToAdd)}
}
else if item is Spacing {
handler = { [weak self] in
self?.checkAndAddSubviewIfNeeded(PipeSpacerView()) }
}
}
func checkAndAddSubviewIfNeeded(_ subviewToAdd: UIView) {
if !myStackView.arrangedSubviews.contains(where: { view in return view.tag == subviewToAdd.tag }) {
self.myStackView.addArrangedSubview(subviewToAdd)
}
}
希望这会有所帮助!