美文网首页
性能优化

性能优化

作者: Abner_XuanYuan | 来源:发表于2023-03-07 00:01 被阅读0次

    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)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件。

    相关文章

      网友评论

          本文标题:性能优化

          本文链接:https://www.haomeiwen.com/subject/keevldtx.html