iOS优化相关学习

屏幕卡顿

CPU和GPU

CPU(Centeral Processing Unit,中央处理器)
对象的创建和销毁,对象属性的调整,布局计算,文本的计算和排版,图片的格式转换和解码、图像的绘制(Core Graphics)

GPU(Graphics Processing Unit,图形处理器)
纹理的渲染

CPU 和 GPU 工作原理

CPU --计算–>GPU–渲染–>帧缓存–读取–>视频控制器–显示–>屏幕

iOS中是双缓存机制,有前帧缓存,后帧缓存

卡顿产生的原因

CPU处理渲染成像还未完成,GPU垂直同步信号VSync已经到达。因此GPU只能展示上一帧的画面,导致丢帧。

解决卡顿的主要思路
  1. 尽可能减少CPU、GPU资源损耗

  2. 按照60FPS的刷新帧率,每隔16ms就会有一次VSync信号

卡顿优化 - CPU
  1. 尽量用轻量级的对象,例如不需要响应事件处理的地方,考虑用CALayer替换UIView

  2. 不要去频繁的调整UIView的相关属性,例如frameboundstransform

  3. 尽量提前计算布局,在有需要的的时候一次性调整,不要多次修改属性

  4. AutoLayout会比直接设置frame消耗更多CPU资源

  5. 图片的size最好刚好与UIimageView的size保持一致

  6. 使用多线程时,控制线程的最大并发量

  7. 尽量把耗时的操作,放在子线程操作

  • 文本处理(尺寸计算、绘制)
[@"text" boundingRectWithSize:CGSizeMake(100,MAXFLOAT)] options:NSStringDrawingUsesLineFragmentOrigin attributes:nil context:nil];
  • 图片处理(解码、绘制)
    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.frame = CGRectMake(100, 100, 100, 100);
    [imageView setImage:[UIImage imageNamed:@"xxxx"]];
    [self.view addSubview:imageView];

[imageView setImage:[UIImage imageNamed:@"xxxx"]];

- (void)image
{
    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.frame = CGRectMake(100, 100, 100, 56);
    [self.view addSubview:imageView];
    self.imageView = imageView;
 
 
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 获取CGImage
        CGImageRef cgImage = [UIImage imageNamed:@"***"].CGImage;
 
 
        // alphaInfo
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage) & kCGBitmapAlphaInfoMask;
        BOOL hasAlpha = NO;
        if (alphaInfo == kCGImageAlphaPremultipliedLast ||
            alphaInfo == kCGImageAlphaPremultipliedFirst ||
            alphaInfo == kCGImageAlphaLast ||
            alphaInfo == kCGImageAlphaFirst) {
            hasAlpha = YES;
        }
 
         // bitmapInfo
        CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
        bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
  
        // size
        size_t width = CGImageGetWidth(cgImage);
        size_t height = CGImageGetHeight(cgImage);
 
         // context 解码
        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGColorSpaceCreateDeviceRGB(), bitmapInfo);
  
        // draw
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
 
         // get CGImage
        cgImage = CGBitmapContextCreateImage(context);
  
        // into UIImage
        UIImage *newImage = [UIImage imageWithCGImage:cgImage];
  
        // release
        CGContextRelease(context);
        CGImageRelease(cgImage);
 
        // back to the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = newImage;
        });
    });
}
优化卡顿 - GPU

由于GPU主要负责纹理的渲染所以我们优化的角度一般从图片图形开始

  • 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
  • GPU能处理的最大纹理尺寸是4096*4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
  • 尽量减少视图数量和层次
  • 减少透明的视图(alpha<1),不透明的就设置opaque为YES
  • 尽量避免出现离屏渲染
/**
在OpenGL中,GPU有2种渲染模式

On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作

Off-Screen Rendering:离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作

离屏渲染消耗性能的原因

需要创建新的缓冲区

离屏渲染的整个过程,需要多次切换上下文环境,
先是从当前屏幕(On-Screen)切换到离屏(Off-Screen),
等到离屏渲染结束后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
*/

// 触发离屏渲染的操作
// 光栅化
self.view.layer.shouldRasterize = YES;

// 遮罩
self.view.layer.mask

/** 圆角,
同时设置layer.maskToBounds = YES、layer.cornerRadius 大于0
考虑通过CoreGraphics绘制裁剪圆角,或者叫美工提供圆角图片

但是iOS9已经优化了这个问题
*/

//self.view.layer.maskToBounds = YES;
//self.view.layer.cornerRadius= 10;


/**
阴影
layer.shadowXXX 
如果设置了layer.shadowPath就不会产生离屏渲染了,不设置默认是环绕layer添加的
*/
卡顿检测
  • 平时所说的”卡顿“主要是因为在主线程执行了比较耗时的操作阻塞了主线程造成的
  • 可以添加Observer到主线程Runloop中,通过监听Runloop状态切换的耗时,以达到监控卡顿的目的
    image.png

    主线程大部分的操作(比如点击事件的处理、view的绘制计算等等)都是在source0source1之间,所以我们只要监控下结束休眠处理source1一直到绕回来处理source0这种所消耗的时间
    LXDAppFluecyMonitor 卡顿检测工具(发现卡顿并且在控制台有输出监测到导致卡顿的方法调用栈)
耗电优化
  1. 尽可能降低CPU、GPU功耗

  2. 少用定时器Timer

  3. 优化I/O操作

    • 尽量不要频繁的使用小数据,最好一次性批量写入
    • 读写大量重要数据时,考虑使用dispatch_io, 其提供了基于GCD的异步操作文件I/O的API。用dispatch_io系统会优化磁盘访问
    • 数据量比较大的,建议使用数据库(比如SQLite、CoreData)
  4. 网络优化

    • 减少、压缩网络数据(数据格式使用JSON,体积就会减少很多。还有现在有很多公司在用protocol buffers更小更快更简单)
    • 如果多次请求的结果是相同的,尽量使用缓存
    • 使用断点续传,否则网络不稳定时可能多次传输相同的内容
    • 网络不可用时, 不要尝试执行网络请求
    • 让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间
    • 批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块的下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次性下载多封,不要一封一封地下载
  5. 定位优化

    • 如果只是需要快速确定用户的位置,最好用CLLocationManagerrequestLocation方法。定位完成后,会自动让定位硬件断电
    • 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务
    • 尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest
      需要后台定位时,尽量设置pausesLocationUpdatesAutomatically为YES
    • 如果用户不太可能移动的时候系统会自动暂停位置更新,尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion:
  6. 硬件检测优化

    • 用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件
启动优化
  1. 启动分为两种:
    • 冷启动(Cold Launch):从零开始启动APP
    • 热启动(Warm Launch):APP已经在内存中,在后台存活着,再次点击图标启动APP
  2. APP启动时间的优化
    • Xcode提供给我们一种分析启动时间的方式


      image.png
    • 如果需要更详细的信息,那就将DYLD_PRINT_STATISTICS_DETAILS设置为1
    • total time在400~500ms之间就相对来说是比较正常的
  3. APP冷启动阶段可以概括为3大阶段
    • dyld
    • runtime
    • main函数
      main函数之前所做的:
image.png

第一阶段dyld(dynamic link editor)
Apple的动态链接器,可以用来装载Mach-O文件(可执行文件、动态库等)

  • 启动APP时,dyld所做的事情有
  • 装载APP的可执行文件,同时会递归加载所有依赖的动态库
  • 当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处

第二阶段 runtime

  • 启动APP时,runtime所做的事情有
  • 调用map_images进行可执行文件内容的解析和处理
  • 在load_images中调用call_load_methods,调用所有Class和Category的+load方法
  • 进行各种objc结构的初始化(注册Objc类,初始化类对象等等)
  • 调用C++静态初始化器和__attribute__((constructor))修饰的函数

到此为止,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,...)都已经按格式成功加载到内存中,被runtime所管理

第三阶段 main
总结一下

  • APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库
  • 并由runtime负责加载成objc定义的结构
  • 所有初始化工作结束后,dyld就会调用main函数
  • 接下来就是UIApplicationMain函数,APPDelegate的application:didFinishLaunchingWithOptions:方法

优化方案

  • dyld
    • 减少动态库、合并一些动态库(定期清理不必要的动态库)
    • 减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类)
    • 减少C++虚函数的数量
    • Swift尽量使用Struct
  • runtime
    • +initialize方法和dispatch_once取代所有的__attribute__((constructor))、C++静态构造器、Objc的+load
  • main
    • 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在finishLaunching方法中按需加载
安装包瘦身
  1. 资源(图片、音频、视频等)
  2. 可执行文件瘦身
    • 编译器优化
      • 设置 Strip Linked ProductMake Strings Read-OnlySymbols Hidden by Default设置为YES(如果项目比较新的话,xcode这些设置是默认为YES的)
      • 去掉异常支持,Enable C++ ExceptionsEnable Objective-C Exceptions设置为NO, Other C Flags添加-fno-exceptions
      • 利用AppCode(收费,可试用30天)检测未使用的代码:菜单栏 -> Code -> Inspect Code
      • 编写LLVM插件检测出重复代码、未被调用的代码
      • LinkMap:生成LinkMap文件,可以查看可执行文件的具体组成
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容