MJiOS底层笔记--性能优化

本文属笔记性质,主要针对本身理解不太透彻的地方进行记录。

推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。ios


CPU和GPU

CPU(Central Processing Unit,中央处理器)

  1. 对象的建立和销毁
  2. 对象属性的调整
  3. 布局计算
  4. 文本的计算和排版
  5. 图片的格式转换和解码
  6. 图像的绘制(Core Graphics)

GPU(Graphics Processing Unit,图形处理器)

  1. 纹理的渲染 用来就是用来给屏幕展现的数据格式

协做工做

  1. CPU负责计算位置、大小、颜色等等一系列参数
  2. GPU负责将数据进行渲染后放入缓冲区备用
  3. 控制器从缓冲区读取数据并展现到屏幕上
  4. 在iOS中是双缓冲机制,有前帧缓存、后帧缓存。能够提升渲染效率

屏幕成像原理

显示器将要展现一页数据git

  1. 发送VSync
  2. 从上至下依次发出HSync逐行进行填充
  3. 循环步骤1

卡顿产生的缘由

VSync信号

  1. 会将缓冲区的数据显示到屏幕上
  2. 立刻让CPU和GPU开始下一帧的处理

60FPS

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


卡顿优化 - CPU

  1. 尽可能用轻量级的对象数据库

    好比用不到事件处理的地方,能够考虑使用CALayer取代UIView缓存

  2. 不要频繁地调用UIView的相关属性bash

    好比frame、bounds、transform等属性,尽可能减小没必要要的修改网络

  3. 尽可能提早计算好布局并发

    在有须要时一次性调整对应的属性,不要屡次修改属性app

  4. Autolayout会比直接设置frame消耗更多的CPU资源异步

  5. 图片的size最好恰好跟UIImageView的size保持一致

    减小ImageView对图片的伸缩操做

  6. 控制一下线程的最大并发数量

  7. 尽可能把耗时的操做放到子线程

    充分利用多核优点

    文本处理(尺寸计算、绘制)

    图片处理(解码、绘制)

    [UIImage imageNamed:@"timg"]加载出来的图片是未解码的,当UIImageView须要被展现的时候才会由CPU进行解码操做。而这个解码操做默认在主线程进行。

    咱们能够将解码操做转移到异步。

- (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:@"timg"].CGImage;

        // 获取图片信息
        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);

        // 建立图形上下文
        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGColorSpaceCreateDeviceRGB(), bitmapInfo);

        // 将图片绘制到上下文中
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);

        // 获取解码后的获取CGImage
        cgImage = CGBitmapContextCreateImage(context);

        // 将解码后的CGImage包装成UIImage
        UIImage *newImage = [UIImage imageWithCGImage:cgImage];

        // 释放资源
        CGContextRelease(context);
        CGImageRelease(cgImage);

        // back to the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            //回到主线程设置图片
            self.imageView.image = newImage;
        });
    });
}
复制代码

卡顿优化 - GPU

  1. 尽可能减小视图数量和层次 每个View都须要被计算并渲染
  2. 尽可能避免短期内大量图片的显示 每张图片都须要被单独渲染,尽量将多张图片合成一张进行显示
  3. GPU能处理的最大纹理尺寸是4096x4096 一旦超过这个尺寸,就会占用CPU资源进行处理,因此纹理尽可能不要超过这个尺寸
  4. 减小透明的视图(alpha<1) 不透明的就设置opaque为YES
  5. 尽可能避免出现离屏渲染

离屏渲染

在OpenGL中,GPU有2种渲染方式

  1. On-Screen Rendering:

    当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操做

  2. Off-Screen Rendering:

    离屏渲染,在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操做

为何会使用离屏渲染

当使用某些效果时,图层的效果处理起来很费时,有可能超过16.67ms致使丢帧。系统会在当前屏幕的缓冲区以外另开辟一个缓冲区去预合成。

在VSync(垂直脉冲)信号做用下,视频控制器每隔16.67ms就会去帧缓冲区(当前屏幕缓冲区)读取渲染后的数据;可是有些效果被认为不能直接呈现于屏幕前,而须要在别的地方作额外的处理,进行预合成。

当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成以前(下一个VSync信号开始前)不能直接在屏幕中绘制,因此就须要屏幕外渲染。

你能够这么理解. 老板叫我短期间内作一个app.我一我的能作,可是时间过短,因此我得让我朋友一块儿来帮着我作.(性能消耗: 也就是耗 你跟你朋友之间沟通的这些成本,多浪费啊).可是没办法 谁让你作不完呢.

离屏渲染消耗性能的缘由

  1. 须要建立新的缓冲区

  2. 频繁的切换缓冲区

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

哪些操做会触发离屏渲染?

  1. 光栅化

    layer.shouldRasterize = YES

  2. 遮罩

    layer.mask

  3. 圆角

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

  4. 阴影,layer.shadowXXX

    若是设置了layer.shadowPath就不会产生离屏渲染

  5. 富文本效果


卡顿检测

平时所说的“卡顿”主要是由于在主线程执行了比较耗时的操做

能够添加Observer到主线程RunLoop中,经过监听RunLoop状态切换的耗时,以达到监控卡顿的目的。

一般是监听Runloop被唤醒到休眠以前这段时间的时长,连续超过阀值必定次数就打印当前主线程的堆栈。

MJ里的项目是LXDAppFluecyMonitor


耗电

主要来源

  1. CPU处理,Processing
  2. 网络,Networking
  3. 定位,Location
  4. 图像,Graphics

耗电优化

  1. 尽量下降CPU、GPU功耗

  2. 少用定时器

  3. 优化I/O操做

尽可能不要频繁写入小数据,最好批量一次性写入

读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操做文件I/O的API。用dispatch_io系统会优化磁盘访问

数据量比较大的,建议使用数据库(好比SQLite、CoreData)

  1. 网络优化

减小、压缩网络数据

若是屡次请求的结果是相同的,尽可能使用缓存

使用断点续传,不然网络不稳定时可能屡次传输相同的内容

网络不可用时,不要尝试执行网络请求

让用户能够取消长时间运行或者速度很慢的网络操做,设置合适的超时时间

批量传输,好比,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。若是下载广告,一次性多下载一些,而后再慢慢展现。若是下载电子邮件,一次下载多封,不要一封一封地下载

  1. 定位优化

若是只是须要快速肯定用户位置,最好用CLLocationManager的requestLocation方法。定位完成后,会自动让定位硬件断电

若是不是导航应用,尽可能不要实时更新位置,定位完毕就关掉定位服务

尽可能下降定位精度,好比尽可能不要使用精度最高的kCLLocationAccuracyBest

须要后台定位时,尽可能设置pausesLocationUpdatesAutomatically为YES,若是用户不太可能移动的时候系统会自动暂停位置更新

尽可能不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion:

  1. 硬件检测优化

用户移动、摇晃、倾斜设备时,会产生动做(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不须要检测的场合,应该及时关闭这些硬件


APP的启动

冷启动 && 热启动

APP的启动能够分为2种

  1. 冷启动(Cold Launch):从零开始启动APP

  2. 热启动(Warm Launch):APP已经在内存中,在后台存活着,再次点击图标启动APP

启动时间的优化

APP启动时间的优化,主要是针对冷启动进行优化

冷启动时间分析

经过添加环境变量能够打印出APP的启动时间分析(Edit scheme -> Run -> Arguments) DYLD_PRINT_STATISTICS设置为1 若是须要更详细的信息,那就将DYLD_PRINT_STATISTICS_DETAILS设置为1

total time: 1.4 seconds (100.0%)
  total images loaded:  257 (0 from dyld shared cache)
  total segments mapped: 764, into 103339 pages with 7230 pages pre-fetched
  total images loading time: 720.70 milliseconds (48.1%)
  total load time in ObjC:  71.93 milliseconds (4.8%)
  total debugger pause time: 539.08 milliseconds (36.0%)
  total dtrace DOF registration time:   0.12 milliseconds (0.0%)
  total rebase fixups:  2,519,273
  total rebase fixups time: 635.12 milliseconds (42.4%)
  total binding fixups: 283,078
  total binding fixups time:  36.50 milliseconds (2.4%)
  total weak binding fixups time:   0.52 milliseconds (0.0%)
  total redo shared cached bindings time:  52.57 milliseconds (3.5%)
  total bindings lazily fixed up: 0 of 0
  total time in initializers and ObjC +load:  31.39 milliseconds (2.0%)
                         libSystem.B.dylib :   2.92 milliseconds (0.1%)
               libBacktraceRecording.dylib :   3.50 milliseconds (0.2%)
                            CoreFoundation :   1.74 milliseconds (0.1%)
                                Foundation :   2.02 milliseconds (0.1%)
                libMainThreadChecker.dylib :  18.89 milliseconds (1.2%)
total symbol trie searches:    132606
total symbol table binary searches:    0
total images defining weak symbols:  20
total images using weak symbols:  61
复制代码

冷启动

APP的启动由dyld主导,将可执行文件加载到内存,顺便加载全部依赖的动态库

并由runtime负责加载成objc定义的结构

全部初始化工做结束后,dyld就会调用main函数

APP的冷启动能够归纳为3大阶段

  1. dyld

    Apple的动态连接器,能够用来装载Mach-O文件(可执行文件、动态库等)

    装载APP的可执行文件,同时会递归加载全部依赖的动态库

    当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理

  2. runtime

    用map_images进行可执行文件内容的解析和处理

    load_images中调用call_load_methods,调用全部ClassCategory+load方法

    进行各类objc结构的初始化(注册Objc类、初始化类对象等等)

    调用C++静态初始化器和__attribute__((constructor))修饰的函数

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

  3. main


安装包瘦身

安装包(IPA)主要由可执行文件、资源组成

资源(图片、音频、视频等)

  1. 采起无损压缩

  2. 去除没有用到的资源:

    github.com/tinymind/LS…

可执行文件瘦身

  1. 编译器优化 Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES

去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO, Other C Flags添加-fno-exceptions

  1. 利用AppCode(www.jetbrains.com/objc/) 检测未使用的代码:

    菜单栏 -> Code -> Inspect Code

  2. 编写LLVM插件检测出重复代码、未被调用的代码

相关文章
相关标签/搜索