如何判断网站seo做的好坏,大港天津网站建设,wordpress刷量插件,搜索引擎推广的方法有哪些转载#xff1a;TableView性能优化
原文链接#xff1a;https://juejin.cn/post/6955731915672387592
tableView性能优化
Cell重用、标识重用
使用 static 修饰重用标识名称能够保证这个标识只会创建一次#xff0c;提高性能。接着调用dequeueReusableCellWithIdentifie…转载TableView性能优化
原文链接https://juejin.cn/post/6955731915672387592
tableView性能优化
Cell重用、标识重用
使用 static 修饰重用标识名称能够保证这个标识只会创建一次提高性能。接着调用dequeueReusableCellWithIdentifier:方法 获取缓存池中的Cell。如果没有就调用 initWithStyle:ReusIdentifier:方法 创建一个新的Cell。注意事先需要调用registerNib/registerClass方法 为 TableView 注册一下重用标识。
动态高度
我们需要实现它的代理来给出高度
objectivec复制代码- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {// return xxx
}这个代理方法实现后上面的 rowHeight 的设置将会变成无效。在这个方法中我们需要提高cell高度的计算效率来节省时间。
自从iOS8之后有了 self-sizing cell的概念cell可以自己算出高度使用self-sizing cell需要满足以下三个条件
1使用 Autolayout 进行 UI布局约束要求cell.contentView的四条边都与内部元素有约束关系。
2指定 TableView 的 estimatedRowHeight属性 的默认值。
3指定 TableView的rowHeight 属性为 UITableViewAutomaticDimension。
ini复制代码- (void)viewDidload {self.myTableView.estimatedRowHeight 44.0;self.myTableView.rowHeight UITableViewAutomaticDimension;
}除了提高cell高度的计算效率之外对于已经计算出的高度我们需要进行缓存对于已经计算过的高度没有必要进行计算第二次。
减少视图的数目
我们在 cell 上添加系统控件的时候实际上系统都会调用底层的接口进行绘制大量添加控件时会消耗很大的资源并且也会影响渲染的性能。当使用默认的 UITableViewCell 并且在它的 ContentView 上面添加控件时会相当消耗性能。所以目前最佳的方法还是继承 UITableViewCell并重写drawRect方法。
重绘操作仍然在 drawRect方法 中完成但是苹果不建议直接调用 drawRect方法当然如果你强直直接调用此方法当然是没有效果的。苹果要求我们调用UIView类中的 setNeedsDisplay方法则程序会自动调用 drawRect方法 进行重绘。调用 setNeedsDisplay 会自动调用 drawRect。
使用hidden隐藏图层
避免动态添加图层。在初始化cell的时候一并将所有图层预先创建好通过hidden属性控制子图层的显示或隐藏因为单纯的显示操作要比创建快的多。
在快速滚动时考虑使用界面外壳 当用户快速滚动列表视图时虽然使用了所有的优化但视图的重用和渲染仍然需要超过 16 毫秒还有可能出现偶发的丢帧现象从而导致不流畅的体验。
在这些情况下使用一个界面外壳是一个较好的选择外壳可以被预先定义它的唯一目的就是告诉终端用户这些部分即将展示一些数据。当滚动速度降低并低于阈值时刷新最终的视图并填充数据。
你可以使用与列表视图相关联的 panGestureRecognizer 属性获取速度值。
// 列表视图的速度
-(void)scrollViewDidScroll:(UIScrollView *)scrollView { CGPoint velocity [tableView.panGestureRecognizer velocityInView:self.view]; self.velocity velocity;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if(fabs(self.velocity.y) 2000) { //返回界面外壳} else {//返回真正的单元格 }
}避免离屛渲染。
开启离屛渲染的代价就是需要新开辟一块新的缓冲区在渲染的过程中还会多次的切换上下文这些都是很消耗性能的。以下情况均会造成离屛渲染:
1. shadows阴影其原因在于需要显示在所有layer内容的下方因此必须被渲染在先。但此时阴影的本体layer和其子layer都还没有被组合到一起只能另外申请一块内存把本体内容都先画好再根据渲染结果的形状添加阴影到帧缓冲区frame buffer最后把内容画上去。不过如果我们能够预先告诉CoreAnmation通过 shadowPath属性 阴影的几何形状那么阴影当然可以先被独立渲染出来不需要依赖layer本体也就不再需要离屏渲染了。
2. 设置了组透明度为YES并且透明度不为1的layer不透明产生离屏渲染的条件是layer.opacity ! 1.0并且有 子layer 或者背景图。alpha并不是分别应用在每一层之上而是只有到 整个layer树 画完之后再统一加上alpha最后和底下其他layer的像素进行组合。显然也无法通过一次遍历就得到最终结果。
3. masks遮罩我们知道mask是应用在layer和其所有子layer的组合之上的而且可能带有透明度那么其实和group opacity的原理类似不得不在离屏渲染中完成。
4. cornerRadiusclipsToBounds容器的子layer因为父容器有圆角那么也会需要被裁剪而这时它们还在渲染队列中排队尚未被组合到一块画布上自然也无法统一裁剪不得已只能另开一块内存来操作。而如果只是设置cornerRadius并不会触发离屏渲染。
5. shouldRasterize光栅化如果layer不是静态的我们更新已光栅化的layer,会造成大量的离屏渲染。 例如UITableViewCell因为复用的原因重绘是很频繁的。如果此时设置了光栅化会造成大量离屏渲染降低性能。 不要过度使用,系统限制了缓存的大小为 2.5 * Screen Size。超出缓存之后,同样会造成大量的离屏渲染。 离屏渲染缓存内容有时间限制被光栅化的图片即缓存内容如果超过100ms没有被使用那么它就会丢弃无法进行复用。所以光栅化只能用在图像内容不变的前提下且只对连续不断使用的图片进行缓存用于避免静态内容的复杂特效的重绘,例如UIBlurEffect用于避免多个View嵌套的复杂View的重绘。
6. edge antialiasing抗锯齿)分页加载数据预先异步请求数据 - Prefetching API
在 viewDidLoad 中先请求网络数据来获取一些初始化数据然后再利用 UITableView 的 Prefetching API 来对数据进行预加载从而来实现数据的无缝加载。
UITableViewDataSourcePrefetching 协议
// this protocol can provide information about cells before they are displayed on screen.protocol UITableViewDataSourcePrefetching NSObjectrequired// indexPaths are ordered ascending by geometric distance from the table view
- (void)tableView:(UITableView *)tableView prefetchRowsAtIndexPaths:(NSArrayNSIndexPath * *)indexPaths;optional// indexPaths that previously were considered as candidates for pre-fetching, but were not actually used; may be a subset of the previous call to -tableView:prefetchRowsAtIndexPaths:
- (void)tableView:(UITableView *)tableView cancelPrefetchingForRowsAtIndexPaths:(NSArrayNSIndexPath * *)indexPaths;end第一个函数会基于当前滚动的方向和速度对接下来的 IndexPaths 进行 Prefetch通常我们会在这里实现预加载数据的逻辑。
第二个函数是一个可选的方法当用户快速滚动导致一些 Cell 不可见的时候你可以通过这个方法来取消任何挂起的数据加载操作有利于提高滚动性能, 在下面我会讲到。
实现这俩个函数的逻辑代码为
swift复制代码extension ViewController: UITableViewDataSourcePrefetching {// 翻页请求func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {let needFetch indexPaths.contains { $0.row viewModel.currentCount}if needFetch {// 1.满足条件进行翻页请求indicatorView.startAnimating()viewModel.fetchImages()}for indexPath in indexPaths {if let _ viewModel.loadingOperations[indexPath] {return}if let dataloader viewModel.loadImage(at: indexPath.row) {print(在 \(indexPath.row) 行 对图片进行 prefetch )// 2 对需要下载的图片进行预热viewModel.loadingQueue.addOperation(dataloader)// 3 将该下载线程加入到记录数组中以便根据索引查找viewModel.loadingOperations[indexPath] dataloader}}}func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]){// 该行在不需要显示的时候取消 prefetch 避免造成资源浪费indexPaths.forEach {if let dataLoader viewModel.loadingOperations[$0] {print(在 \($0.row) 行 cancelPrefetchingForRowsAt )dataLoader.cancel()viewModel.loadingOperations.removeValue(forKey: $0)}}}
}最后再加上俩个有用的方法该功能就大功告成了 // 用于计算 tableview 加载新数据时需要 reload 的 cellfunc visibleIndexPathsToReload(intersecting indexPaths: [IndexPath]) - [IndexPath] {let indexPathsForVisibleRows tableView.indexPathsForVisibleRows ?? []let indexPathsIntersection Set(indexPathsForVisibleRows).intersection(indexPaths)return Array(indexPathsIntersection)}// 用于确定该索引的行是否超出了目前收到数据的最大数量func isLoadingCell(for indexPath: IndexPath) - Bool {return indexPath.row (viewModel.currentCount)}滑动TableView时按需加载内容
有些情况下我们可能会去快速的滑动列表这时候其实会有大量的cell对象被创建、被重用但其实我们可能只是去浏览列表停止的那一页的上下一定范围内的信息前面快速划过的那些信息对我们来说都是无用的。此时我们可以通过ScrollView的代理方法
scrollViewWillEndDragging: withVelocity: targetContentoffset:来按需加载内容。
#pragma mark - UIScrollViewDelegate
//按需加载 - 如果目标行与当前行相差超过指定行数只在目标滚动范围的前后指定3行加载。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { NSIndexPath *targetPath [_myTableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset-y)]; NSIndexPath *firstVisiblePath [[_myTableView indexPathsForVisibleRows] firstObject]; NSInteger skipCount 8; if (labs(firstVisiblePath.row - targetPath.row) skipCount) { NSArray *temp [_myTableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset-y, _myTableView.frame.size.width, _myTableView.frame.size.height)]; NSMutableArray *arr [NSMutableArray arrayWithArray:temp]; if (velocity.y0) { NSIndexPath *indexPath [temp lastObject]; if (indexPath.row33) { [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]]; [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]]; [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]]; } } [_dataList addObjectsFromArray:arr]; }
} targetContentOffset 是 TableView 减速到停止的地方, velocity 表示速度向量。
如何避免滚动时的卡顿: 异步化UI不要阻塞主线程
当你遇到滚动卡顿的应用程序时通常是由于任务长时间运行阻碍了 UI 在主线程上的更新想让主线程有空来响应这类更新事件第一步就是要将消耗时间的任务交给子线程去执行避免在获取数据时阻塞主线程。
苹果提供了很多为应用程序实现并发的方式例如 GCD我在这里对 Cell 上的图片进行异步加载使用的就是它。 代码如下
swift复制代码class DataLoadOperation: Operation {var image: UIImage?var loadingCompleteHandle: ((UIImage?) - ())?private var _image: ImageModelprivate let cachedImages NSCacheNSURL, UIImage()init(_ image: ImageModel) {_image image}public final func image(url: NSURL) - UIImage? {return cachedImages.object(forKey: url)}override func main() {if isCancelled {return}guard let url _image.url else {return}downloadImageFrom(url) { (image) inDispatchQueue.main.async { [weak self] inguard let ss self else { return }if ss.isCancelled { return }ss.image imagess.loadingCompleteHandle?(ss.image)}}}// Returns the cached image if available, otherwise asynchronously loads and caches it.func downloadImageFrom(_ url: NSURL, completeHandler: escaping (UIImage?) - ()) {// Check for a cached image.if let cachedImage image(url: url) {DispatchQueue.main.async {print(命中缓存)completeHandler(cachedImage)}return}URLSession.shared.dataTask(with: url as URL) { data, response, error inguardlet httpURLResponse response as? HTTPURLResponse, httpURLResponse.statusCode 200,let mimeType response?.mimeType, mimeType.hasPrefix(image),let data data, error nil,let _image UIImage(data: data)else { return }// Cache the image.self.cachedImages.setObject(_image, forKey: url, cost: data.count)completeHandler(_image)}.resume()}
}在willDisplayCell:forRowAtIndexPath:代理方法中绑定数据
那具体如何使用呢别急听我娓娓道来这里我再给大家一个小建议大家都知道 UITableView 实例化 Cell 的方法是tableView:cellForRowAtIndexPath: 相信很多人都会在这个方法里面去进行数据绑定然后更新 UI其实这样做是一种比较低效的行为因为这个方法需要为每个 Cell 调用一次它应该快速的执行并返回重用 Cell 的实例不要在这里去执行数据绑定因为目前在屏幕上还没有 Cell。我们可以在 tableView:willDisplayCell:forRowAtIndexPath: 这个方法中进行数据绑定这个方法在显示cell之前会被调用。
为每个 Cell 执行下载任务的实现代码如下
swift复制代码 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) - UITableViewCell {guard let cell tableView.dequeueReusableCell(withIdentifier: PreloadCellID) as? ProloadTableViewCell else {fatalError(Sorry, could not load cell)}if isLoadingCell(for: indexPath) {cell.updateUI(.none, orderNo: \(indexPath.row))}return cell}func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {// preheat image 处理将要显示的图像guard let cell cell as? ProloadTableViewCell else {return}// 图片下载完毕后更新 celllet updateCellClosure: (UIImage?) - () { [unowned self] (image) incell.updateUI(image, orderNo: \(indexPath.row))viewModel.loadingOperations.removeValue(forKey: indexPath)}// 1. 首先判断是否已经存在创建好的下载线程if let dataLoader viewModel.loadingOperations[indexPath] {if let image dataLoader.image {// 1.1 若图片已经下载好直接更新cell.updateUI(image, orderNo: \(indexPath.row))} else {// 1.2 若图片还未下载好则等待图片下载完后更新 celldataLoader.loadingCompleteHandle updateCellClosure}} else {// 2. 没找到则为指定的 url 创建一个新的下载线程print(在 \(indexPath.row) 行创建一个新的图片下载线程)if let dataloader viewModel.loadImage(at: indexPath.row) {// 2.1 添加图片下载完毕后的回调dataloader.loadingCompleteHandle updateCellClosure// 2.2 启动下载viewModel.loadingQueue.addOperation(dataloader)// 2.3 将该下载线程加入到记录数组中以便根据索引查找viewModel.loadingOperations[indexPath] dataloader}}}对预加载的图片进行异步下载预热
swift复制代码func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {let needFetch indexPaths.contains { $0.row viewModel.currentCount}if needFetch {// 1.满足条件进行翻页请求indicatorView.startAnimating()viewModel.fetchImages()}for indexPath in indexPaths {if let _ viewModel.loadingOperations[indexPath] {return}if let dataloader viewModel.loadImage(at: indexPath.row) {print(在 \(indexPath.row) 行 对图片进行 prefetch )// 2 对需要下载的图片进行预热viewModel.loadingQueue.addOperation(dataloader)// 3 将该下载线程加入到记录数组中以便根据索引查找viewModel.loadingOperations[indexPath] dataloader}}}取消 Prefetch 时cancel 任务避免造成资源浪费
swift复制代码func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]){// 该行在不需要显示的时候取消 prefetch 避免造成资源浪费indexPaths.forEach {if let dataLoader viewModel.loadingOperations[$0] {print(在 \($0.row) 行 cancelPrefetchingForRowsAt )dataLoader.cancel()viewModel.loadingOperations.removeValue(forKey: $0)}}}