1、启动相关优化
1、Main 函数执行前
1、加载可执行文件(mach-o文件)。
2、加载动态链接库,进行 rebase 指针调整和 bind 符号绑定。
3、Objc 运行时的初始化处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查
4、初始化,包括执行了+load() 方法、attribute((constructor)) 修饰的函数调用、创建 C++ 静态全局变量。
优化方案:
1、减少动态库加载。动态库较多时,尽量将多个动态库进行合并。
2、减少非启动所需类和方法,或延迟加载。
3、尽量减少 +load() 方法使用,尽量用 +initialize() 方法替换 +load()方法。
4、尽量减少 C++ 全局变量的数量。
2、Main函数执行后
从 main() 函数执行到 Appdelegate 的 didFinishLaunchingWithOptions 方法执行完成。
1、首屏初始化所需要配置文件的读写操作。
2、首屏列表大数据的读取。
3、首屏渲染的大量计算。
优化方案
尽量减少首屏加载内容,非首屏数据不加载,非首屏必要数据不加载,首屏必要数据按重要性分批次加载。
3、启动监控方案
1、定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。
2、对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时。
2、卡顿
1、CPU&GPU
CPU 任务:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制(Core Graphics)等。
GPU 任务:渲染。
优化方案
1、尽量减少 CPU、GPU 任务。
2、尽量用轻量级的对象。
3、尽量减少属性的调用次数和修改。
4、尽量提前计算好布局。
5、Autolayout 会比直接设置 frame 消耗更多的 CPU 资源。
6、UIImageView 大小尽量和图片大小一致。
7、尽量减少线程的最大并发数量。
8、有大量图片时做到按需加载。
9、GPU 能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸。
10、尽量减少视图数量和层次。
11、减少透明的视图(alpha<1),不透明的就设置 opaque 为YES。
12、尽量把耗时的操作放到子线程(文本尺寸、图片处理)。
2、UITableView 优化
1、善用重用标识。
2、设置预估行高,预先缓存动态行高。
对一些 cell 高度不固定的 Cell 要做一下缓存 cell 高度。获取到数据后子线程计算高度并写到 model 的 cellHeight 属性中,使用时读取 cellHeight 。
3、减少 SubViews 层级、异步绘制、避免离屏渲染、使用 Hidden 隐藏图层。
4、分屏加载数据,预先异步请求数据。
// 先请求网络数据来获取一些初始化数据,然后再利用 UITableView 的 Prefetching API 来对数据进行预加载,从而来实现数据的无缝加载。
UITableViewDataSourcePrefetching 协议
// this protocol can provide information about cells before they are displayed on screen.
@protocol UITableViewDataSourcePrefetching <NSObject>
@required
//基于当前滚动的方向和速度对接下来的 IndexPaths 进行 Prefetch,通常我们会在这里实现预加载数据的逻辑
// indexPaths are ordered ascending by geometric distance from the table view
- (void)tableView:(UITableView *)tableView prefetchRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
@optional
//当用户快速滚动导致一些 Cell 不可见的时候,你可以通过这个方法来取消任何挂起的数据加载操作,有利于提高滚动性能
// 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:(NSArray<NSIndexPath *> *)indexPaths;
@end
//使用
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 的 cell
func 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)
}
5、滑动 TableView 时,按需加载内容。
#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.y<0) {
NSIndexPath *indexPath = [temp lastObject];
if (indexPath.row+33) {
[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 表示速度向量。
6、cell 类中应该避免请求网络加载数据。
7、在 willDisplayCell:forRowAtIndexPath: 代理方法中的绑定数据。
在 tableView: cellForRowAtIndexPath: 方法中获取/创建 cell ,在 willDisplayCell:forRowAtIndexPath: 绑定数据,这个方法在显示cell之前会被调用。
8、尽量避免离屏渲染。
9、局部刷新。
3、耗电优化
1、尽可能降低 CPU/GPU 功耗。
2、少用定时器。
3、优化I/O操作。
4、尽量不要频繁写入小数据,最好批量一次性写入。
5、读写大量重要数据时,考虑用 dispatch_io,其提供了基于 GCD 的异步操作文件I/O的API。用 dispatch_io 系统会优化磁盘访问。
6、数据量比较大的,建议使用数据库(比如SQLite、CoreData)。
4、网络优化
1、减少、压缩网络数据。
2、如果多次请求的结果是相同的,尽量使用缓存。
3、使用断点续传,否则网络不稳定时可能多次传输相同的内容。
4、网络不可用时,不要尝试执行网络请求。
5、让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间。
6、批量传输、下载,避免多次调用接口。
5、定位优化
1、如果只是需要快速确定用户位置,最好用 CLLocationManager的requestLocation 方法。定位完成后,会自动让定位硬件断电。
2、如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务。
3、尽量降低定位精度,比如尽量不要使用精度最高的 kCLLocationAccuracyBest。
4、需要后台定位时,尽量设置 pausesLocationUpdatesAutomatically 为 YES,如果用户不太可能移动的时候系统会自动暂停位置更新。
5、尽量不要使用 startMonitoringSignificantLocationChanges,优先考虑 startMonitoringForRegion: 。
6、用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件。
网友评论