IOS UITableView性能优化

一、UITableView是什么?

UITableView是一个强大的视图组件,它可以显示一个滚动的列表数据。每个列表项被称为一个单元格(UITableViewCell)。UITableView的数据源(通常是一个数组)和委托(通常是视图控制器)提供了列表的内容和行为。

二、UITableView什么工作原理?

理解UITableView的工作原理对于优化其性能和用户体验至关重要。当你滚动表格时,UITableView并不会为所有单元格创建视图。相反,它使用了一种叫做"单元格重用"的技术,只创建屏幕上可见的单元格,并且当这些单元格滚动出屏幕时,会将其放入一个重用队列中。当新的单元格滚动到屏幕上时,UITableView会从重用队列中取出一个单元格,并对其进行配置。这就是为什么在cellForRowAt方法中,你总是需要配置单元格的所有属性------因为你无法确定这个单元格是新的,还是从重用队列中取出的。

三、 UITableView如何优化?

了解完上面的工作原理,我们就可以从以下几个方面进行优化

● 重用单元格

● 异步加载图片

● 预估单元格高度

● 避免离屏渲染

● 避免图片尺寸不一致

● 避免动态添加内容

● 滑动按需加载

1. 重用单元格

在UITableView中,单元格的重用是提高性能的关键。通过重用单元格,我们可以避免频繁地创建和销毁单元格对象,从而减少内存分配和释放的开销。

要实现单元格的重用,我们需要注册单元格的重用标识符,并在cellForRowAtIndexPath方法中使用dequeueReusableCellWithIdentifier方法获取可重用的单元格。下面是一个简单的示例:

swift 复制代码
// 注册单元格的重用标识符  
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")  
// 在cellForRowAtIndexPath方法中获取可重用的单元格  
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {  
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)  
    // 配置单元格的数据和界面  
    return cell  
}

2. 异步加载图片

在UITableView中加载图片时,如果图片较大或者网络加载较慢,可能会导致滚动卡顿。为了解决这个问题,我们可以使用异步加载图片的方式。

异步加载图片的核心思想是在后台线程中加载图片,然后在主线程中更新UI。这样可以避免阻塞主线程,提高滚动的流畅性。下面是一个使用SDWebImage库进行异步加载图片的示例:

swift 复制代码
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {  
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)  
    let imageUrl = "https://xxxxx/xxAA.jpg"  
    cell.imageView?.sd_setImage(with: URL(string: imageUrl), placeholderImage: UIImage(named: "placeholder"))  
    return cell  
}

3. 预估单元格高度

当UITableView包含可变高度的行时,如果逐行计算高度,会导致性能开销巨大,因为这种方法需要对每一行进行高度计算,从而增加了CPU和GPU的负担。因此为了优化性能,可以使用estimatedRowHeight属性来为单元格设置预估高度。

estimatedRowHeight属性允许您为UITableView设置一个估计的行高,以便在加载表格时预先分配足够的空间,而不需要逐行计算高度。通过使用estimatedRowHeight属性,您可以避免因逐行计算高度而产生的性能瓶颈,从而提高表格的滚动性能和响应速度。

需要注意的是,estimatedRowHeight只是一种估计值,实际行高可能会根据内容的不同而有所差异。因此,在使用estimatedRowHeight时,您需要根据表格中内容的特性和规律来选择一个合适的估计值,以确保表格的显示效果和性能达到一个良好的状态。

ini 复制代码
tableView.estimatedRowHeight = 10.0f;//预估行高度

4. 避免离屏渲染

使用圆角、阴影等视图功能的时候,会进行离屏渲染,而离屏渲染跟当前屏幕渲染又不同。它会单独开辟一块缓冲区进行渲染操作,这个过程相对较耗资源。

(1)对于圆角较好的做法是让美术提供圆角背景图,并给Cell设成背景图片。

(2)对于阴影效果可以通过以下代码设置

ini 复制代码
import UIKit  
  
extension UIImageView {  
    func addShadow() {  
        layer.masksToBounds = false  
        layer.shadowColor = UIColor.black.cgColor  
        layer.shadowOffset = CGSize(width: 0, height: 0)  
        layer.shadowOpacity = 0.5  
        layer.shadowRadius = 2  
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath  
    }  
}

5.避免图片尺寸不一致

为了提高UITableView的性能,图片的尺寸应该与UIImageView的尺寸保持一致。通过将图片的尺寸调整为与UIImageView的尺寸相同,可以避免不必要的缩放和拉伸,从而减少计算资源和内存的占用。

此外,图片的contentMode也会对UITableView的滚动速度造成影响。如果图片的contentMode不正确,可能会导致图片加载速度变慢,从而影响应用程序的性能。因此,需要根据需要显示的图片大小来处理contentMode,以优化图片的显示效果。

5. 避免动态添加内容

在初始化UITableViewCell时,将所有需要展示的子视图添加到cell中,避免动态添加。这些子视图可以是自定义的UI元素,例如文本标签、图像视图、按钮等。

优化方法:添加子视图后,可以根据需要设置它们的隐藏属性,以控制它们的显示和隐藏。

通过在初始化时将所有子视图添加到cell中,可以确保它们在显示时已经存在,并且可以在需要时轻松地显示或隐藏它们。这种方法适用于在cell中展示动态内容的情况,例如根据数据源动态设置不同的视图内容。

需要注意的是,如果在初始化时添加了过多的子视图,可能会占用额外的内存和计算资源。因此,在添加子视图时应该考虑到这一点,并根据需要合理地管理内存和计算资源的使用。

6. 滑动按需加载

滑动按需载可分为两种:

● 分页加载

分页加载是指当用户滚动到UITableView底部时,自动加载下一页数据。这可以通过监听UITableView的滚动事件来实现。以下是一个示例代码:

swift 复制代码
func scrollViewDidScroll(_ scrollView: UIScrollView) {  
    let offsetY = scrollView.contentOffset.y  
    let contentHeight = scrollView.contentSize.height  
    let scrollViewHeight = scrollView.frame.height  
      
    if offsetY > contentHeight - scrollViewHeight {  
        // 加载下一页数据  
        loadNextPageData()  
    }  
}

● 滑动范围加载

滑动范围前后加载是指只在目标滚动范围的前后指定 3 行加载。这么处理是因为用户在快速滑动的过程中,大量快速滑动的内容是无用的。所以我们只需要加载最后滑动范围的内容即可。

less 复制代码
// 按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。  
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {  
    // 获取目标偏移量对应的索引路径  
    let ip = indexPathForRow(at: CGPoint(x: 0, y: targetContentOffset.pointee.y))  
    // 获取当前可见的第一个索引路径  
    if let cip = indexPathsForVisibleRows?.first {  
        let skipCount = 10 // 指定行数  
        // 如果目标行与当前行相差超过指定行数  
        if abs(cip.row - ip.row) > skipCount {  
            // 获取目标滚动范围内的所有索引路径  
            let temp = indexPathsForRows(in: CGRect(x: 0, y: targetContentOffset.pointee.y, width: self.width, height: self.height))  
            var arr = temp as NSArray as? [IndexPath] ?? []  
            // 如果滚动方向是向上  
            if velocity.y < 0 {  
                if let indexPath = temp.last, indexPath.row > 3 {  
                    // 在目标滚动范围前添加3个索引路径  
                    arr.append(IndexPath(row: indexPath.row-3, section: 0))  
                    arr.append(IndexPath(row: indexPath.row-2, section: 0))  
                    arr.append(IndexPath(row: indexPath.row-1, section: 0))  
                }  
            }  
            // 将需要加载的数据添加到数组中  
            self.needLoadDatas.append(contentsOf: arr)  
        }  
    }  
}

四、 总结

本文介绍 UITableView 性能优化的思路,在具体的业务应用中,可能会面临不同的场景和不同的需求,需要具体问题具体分析,但万变不离其中,如果大家有其它优化思路、疑问或者复杂场景,欢迎评论区一起交流~

相关推荐
/**书香门第*/4 小时前
Laya ios接入goole广告,搭建环境 1
ios
wakangda10 小时前
React Native 集成 iOS 原生功能
react native·ios·cocoa
Swift社区14 小时前
Excel 列名称转换问题 Swift 解答
开发语言·excel·swift
crasowas1 天前
iOS - 超好用的隐私清单修复脚本(持续更新)
ios·app store
ii_best1 天前
ios按键精灵脚本开发:ios悬浮窗命令
ios
Code&Ocean2 天前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip
/**书香门第*/2 天前
Laya ios接入goole广告,开始接入 2
ios
东坡肘子2 天前
肘子的 Swift 周报 #063|异种肾脏移植取得突破
swiftui·swift·apple
恋猫de小郭2 天前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
网安墨雨2 天前
iOS应用网络安全之HTTPS
web安全·ios·https