UICollectionView 滚动卡顿/延迟

3
在我的iOS项目中,我使用一个常规的UICollectionView和自定义单元格。
当某些属性被设置时,该单元格会接收一些属性,并对该单元格执行一些UI更新。
但是当我滚动时,我感觉到轻微的卡顿,但我不知道如何改进我的代码以使其运行更顺畅。
我已经运行了Intruments并运行了时间分析器,当出现这些延迟时,主线程CPU使用率达到100%,如您所见:

main thread spikes

追踪仪器使用百分比得出以下结论:

trace 1

进一步追踪 png_read_IDAT_data

trace 2

现在来看代码:
在我的UICollectionViewController cellForItem(:_)方法中:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: homeCellId, for: indexPath) as! HomePostCollectionViewCell
    cell.post = self.posts[indexPath.item]
    cell.delegate = self
    cell.layer.cornerRadius = 5
    cell.layer.borderWidth = 0.7
    cell.layer.borderColor = UIColor.rgb(red: 204, green: 204, blue: 204).cgColor
    cell.layer.masksToBounds = true
    return cell     
}

每当post变量被设置时,我会在我的UICollectionViewCell内执行以下UI更新:

public var post: Post? {
    didSet {
        guard let post = self.post else { return }
        userProfileImageView.image = post.user.profileImage
        nameLabel.text = post.user.name
        titleTextView.text = post.title
        creationDateLabel.text = post.creationDate.timeAgoDisplay()

        thumbnailImageView.image = post.thumbnail
        if backgroundImageOffset != nil {
            setBackgroundImageOffset(imageOffset: backgroundImageOffset)
        } else {
            setBackgroundImageOffset(imageOffset: CGPoint.zero)
        }
    }
}

你能帮我追踪问题并改进它吗?

谢谢。

编辑

我已经注释掉了cell.post=这一行,通过这样做,主线程CPU使用率降低到约10%,因此可以确定问题出现在post { didSet {} }构造函数内部。

你能看到有什么有用的改进吗?

编辑2

根据要求,这是timeAgoDisplay()函数的代码

func timeAgoDisplay() -> String {
    let secondsAgo = Int(Date().timeIntervalSince(self))

    let minute = 60
    let hour = 60 * minute
    let day = 24 * hour
    let week = 7 * day
    let month = 4 * week

    let quotient: Int
    let unit: String
    if secondsAgo < minute {
        quotient = secondsAgo
        unit = "second"
    } else if secondsAgo < hour {
        quotient = secondsAgo / minute
        unit = "min"
    } else if secondsAgo < day {
        quotient = secondsAgo / hour
        unit = "hour"
    } else if secondsAgo < week {
        quotient = secondsAgo / day
        unit = "day"
    } else if secondsAgo < month {
        quotient = secondsAgo / week
        unit = "week"
    } else {
        quotient = secondsAgo / month
        unit = "month"
    }

    return "\(quotient) \(unit)\(quotient == 1 ? "" : "s") ago"
}

请注意,在cell.layer.stuff方法中只能在单元格初始化方法(awakeFromNib()init(frame:style))中执行一次。此外,我认为timeAgoDisplay()方法也可以进行优化。这是什么代码?你每次都要在其中初始化一个新的dateFormatter吗?如果是,请每次使用相同的dateFormatter(创建一个静态/单例)。这些是小的改进,也许与你整个问题无关。 - Larme
@Larme 为该函数创建一个单例似乎是一个不错的选择。它目前是一个 Date() 扩展函数 - Ivan Cantarino
你能通过网络图片URL获取图片吗? - Manish Mahajan
没问题,我以为你用了DateFormatter。请注释与setBackgroundImageOffset(imageOffset:)和/或userProfileImageView.image =相关的内容。检查一下是否有改进。这些图片的尺寸是多少? - Larme
1
@ManishM.Mahajan 我实际上是在后台线程上进行所有处理,当它们准备好时,我会分派到主线程。现在我得出结论,问题可能出在缩略图上,我认为它们可能太大了...我正在进行一些测试。 - Ivan Cantarino
显示剩余16条评论
3个回答

2
我也曾经遇到过和您类似的问题,认为可以在后台线程上使用UIImage(data:),然后只需将结果图像应用于main队列上的UIImageView
然而事实并非如此,引用这个主题上一个很好的回答UIImage initWithData: blocking UI thread from async dispatch?
"UIImage不会读取和解码图像,直到第一次实际使用/绘制它。要在后台线程上强制执行此工作,必须在进行主线程-setImage:调用之前在后台线程上使用/绘制图像。许多人发现这种方法反直觉。我在另一个答案中详细解释了这一点。"
如上所述,链接中的答案显示,您需要在使用图像之前在后台线程上use/draw它。
以下是我发现的一个示例,可以在后台线程上强制解压缩: 预加载UIImage以获得超流畅的交互。特别适用于使用JPG的情况,否则会在主线程上产生明显的延迟。 希望这可以帮到您 :)

哇...这可能真的是问题所在!我会仔细阅读它,以便能够正确诊断。 - Ivan Cantarino
试一试,你会发现它会有奇效。同时确保你使用的图像尺寸尽可能接近最终尺寸,因此不要为小缩略图使用1000x1000的图像,因为那会降低滚动性能... - Ladislav
1
这是我注意到的另一个问题,我认为缩略图图像比必要的尺寸要大,我也会在那里做一些改进。我还注意到另一件事情是,我是在layoutSubviews()中使用AutoLayout绘制单元格对象,而不是在init(frame:)中。这导致我不必要地重绘整个单元格的UI内容,因为随着滚动,layoutSubviews()被调用多次。 - Ivan Cantarino
这个解决方案有帮助吗? - Ladislav

1

啊,我曾经遇到过完全相同的问题。每次滚动时,它都会试图重新计算其布局属性。为了解决这个问题,请尝试在您的自定义单元格类中覆盖preferredLayoutAttributesFitting方法,如下所示:

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        return layoutAttributes
    }

哇,这个方法解决了我的问题!但是为什么呢? - Jan

0

我会调查一下。我目前正在运行一些测试,试图缩小问题的范围,然后我会执行你的建议。谢谢。 - Ivan Cantarino
我曾经遇到过同样的问题,这个方法对我很有帮助。它可以将你的滚动加载时间减少一半以上。我强烈推荐你使用。 - Vikram Sinha

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