V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tunnyios
V2EX  ›  iOS

iOS 性能优化~使用或接触过的皮毛分享(修改排版)

  •  
  •   tunnyios · 2016-05-22 21:02:24 +08:00 · 2767 次点击
    这是一个创建于 3141 天前的主题,其中的信息可能已经有所发展或是发生改变。

    iOS 性能优化~使用或接触过的皮毛分享

    虽然 iPhone 性能越来越好,但是 App 也越来越复杂,所以性能问题也从来都是移动开发者所关心的问题之一。一个 App 性能好的表现主要是在:应用启动快速、 UI 反馈响应及时、列表滚动操作流畅、内存使用合理,更不能随随便便 Crash 。

    常见性能优化技巧与策略

    网上有一大堆性能优化的 Tips ,宗旨是写代码之前规避一些性能问题,时常对代码进行性能方面的提升,列出一些个人碰到过的。

    1 、 善用重用和延迟加载

    • 像 tableView 一样,不要一下子创建所有的 subViews ,在需要的时候再创建、将 view 放到一个可重用的列表中。这样只需要在滚动发生时创建你的 views ,避免了不划算的内存分配; 重用一些创建初始化需要重大开销的对象,通过添加属性到你的 class 里或者创建静态变量来实现。NSDateFormatterSQLite 语句正则表达式的初始化和设置都很昂贵。

    2 、尽量把 views 设置为不透明即 opaque 属性为 YES 。

    • (opaque)这个属性给渲染系统提供了一个如何处理这个 view 的提示。如果设为 YES , 渲染系统就认为这个 view 是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为 NO ,渲染系统正常地和其它内容组成这个 View 。默认值是 YES 。

    3 、避免过于复杂的 xib 文件

    • 当加载一个 xib 的时候所有内容都被放在了内存里,包括任何图片。如果有一个不会即刻用到的 view ,就是在浪费宝贵的内存资源了。 Storyboards 就是另一码事儿了, storyboard 仅在需要时实例化一个viewController.

    4 、尽量避免阻塞主线程

    • UIKit 在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成。界面卡顿就是主线程被阻塞的表现。尽量把耗时操作放到其他线程中来做,然后回到主线程来刷新界面。大部分耗时操作牵涉到读写外部资源的 I/O 操作,比如存储(读写文件)或者收发网络数据。可以使用 GCD 来进行多线程操作的,但是一个 App 的线程最好不要超过 5 条。原因 CPU 会在 N 条线程之间调度,消耗大量 CPU 资源; 每条线程被调度执行的频次会降低(线程的执行效率降低)
    • 为了得到更流畅的交互体验, iOS 已经将很多事情放到了其他线程中去做,比如:View 和 layer 的动画绘制前的计算Layer 的组合计算( drawing 后的叠加)PNG 的解码

    5 、 Cache 缓存

    • 善用缓存,缓存一些经常要使用的,不易改变的。比如一些整个 App 都需要用的模型对象,或者一些计算数据。

    6 、权衡渲染方法(又一个性能与空间的权衡)

    • 使用事先渲染好的图片会对性能更好一点,但是图片很多会增加 bundle 的大小;但是如果在代码中不断修改某张图用作不同的用途,会给 App 增加性能负担,所以利弊就有你自己来权衡咯

    7 、内存警告处理

    • 如果你的 app 收到了内存警告,它就需要尽可能释放更多的内存。最佳方式是移除对缓存,图片 object 和其他一些可以重创建的 objects 的 strong references.
    • UIKit 提供了几种收集低内存警告的方法: (一旦收到这类通知,你就需要释放任何不必要的内存使用)
    1. AppDelegate中使用applicationDidReceiveMemoryWarning:的方法
    2. 在你的自定义 UIViewController 的子类(subclass)中覆盖didReceiveMemoryWarning
    3. 注册并接收UIApplicationDidReceiveMemoryWarningNotification 的通知
    • 例如, UIViewController 的默认行为是移除一些不可见的 view , 它的一些子类则可以补充这个方法,删掉一些额外的数据结构。一个有图片缓存的 app 可以移除不在屏幕上显示的图片。
    • 例如,当整个项目的 image 都由 SDWebImage 来处理,可能 SDWebImage 越来越大,因此需要在任何一个控制器存在内存警告时,做清理操作。清理操作要在 Appdelegate 里面做:1.取消正在下载的操作 2.清除内存缓存

    App 性能的关注点

    本文针对用户响应内存方面,简单介绍一下如何进行性能分析。 用户响应 用户响应即用户事件被 runloop 及时处理和响应。 runloop 其实就是 App 事件驱动的一个大循环,使程序一直运行,并接受用户输入。它有一个事件队列,决定程序在何时处理哪些事件, runloop 中通过消息队列防止出现主调方一直等待的情况。 runloop 节省 CPU 时间,没事儿的时候就睡眠,不占用 CPU 时间。有事件唤醒,就开始处理。

    为了让主线程的 runloop 更好的响应用户事件,程序员就应该尽量减少主线程干重活的时间。使用 InstrumentsTime Profiler工具中的Recod thread waiting选项可以统计出 app 运行时各个线程中的阻塞系统调用情况,例如文件读写 read/write ,网络读写 send/recv ,加锁 psynch_mutex_wait 等。Instruments中的System Trace工具则能够记录所有的底层系统调用。

    使用InstrumentsTime Profiler工具分析

    Separate By Thread:线程分离,只有这样才能在调用路径中能够清晰看到占用 CPU 最大的线程.

    Invert Call Tree:从上到下跟踪堆栈信息.这个选项可以快捷的看到方法调用路径最深方法占用 CPU 耗时,比如 FuncA{FunB{FunC}},勾选后堆栈以 C->B->A 把调用层级最深的 C 显示最外面.

    Hide System Libraries:这个就更有用了,勾选后耗时调用路径只会显示 app 耗时的代码,性能分析普遍我们都比较关系自己代码的耗时而不是系统的.基本是必选项.注意有些代码耗时也会纳入系统层级,可以进行勾选前后前后对执行路径进行比对会非常有用.

    Flattern Recursion:拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。

    Top Functions:找到最耗时的函数或方法。

    一般情况下勾选Separate By ThreadHide System Libraries即可;然后选择一个耗时的操作,双击定位到相应的代码页。

    内存

    一个 app 的内存分三类:

    • Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).
    • Abandoned memory: Memory still referenced by your application that has no useful purpose.
    • Cached memory: Memory still referenced by your application that might be used again for better performance.

    其中 Leaked memoryAbandoned memory 都属于应该释放而没释放的内存,都是内存泄露,而 Leaks 工具只负责检测 Leaked memory,而不管 Abandoned memory。在 MRC 时代 Leaked memory 很常见,因为很容易忘了调用 release ,但在 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory, Leaks 工具查不出这类内存泄露,应用有限。循环引用相关的问题,可以查看以前的文章。

    使用InstrumentsLeaks工具分析

    由于 Leaks 是动态监测,所以我们需要手动操作 APP,一边操作,一边观察 Leaks 的变化,当出现红色叉时,就监测到了内存泄露,点击右上角的第二个,进行暂停检测(也可继续检测,当多个时暂停,一次处理了多个).选中有红色柱子的 Leaks ,下面有个"田"字方格,点开,选中Call Tree,在这个界面的右下角有若干选框,选中Invert Call TreeHide System Libraries。选中显示的若干条中的一条,双击,会自动跳到内存泄露代码处.

    推荐使用 MLeaksFinder 第三方框架来进行内存泄露的检测。 MLeaksFinder 目前能自动检测 UIViewControllerUIView 对象的内存泄露,而且也可以扩展以检测其它类型的对象。*MLeaksFinder 的 gitHub 地址*

    原理 :当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view , view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController ,它的 view , view 的 subviews 等等是否还存在,来检测内存泄露。

    当发生内存泄露的时候, MLeaksFinder 会在控制台打印出哪一个 view 没有释放,导致哪一个控制器没有释放,很明显。同时也可以自己设置不监控某个特殊的控制器。

    预防性能问题

    App 的性能问题主要是靠平时的注重而不是最后出问题了的检查。还可以平时配合一些小工具来进行编码。例如:内存泄露检测工具 MLeaksFinder 。

    2 条回复    2016-05-24 12:57:22 +08:00
    ichanne
        1
    ichanne  
       2016-05-23 08:15:19 +08:00 via iPhone
    写的很好,希望每个卡顿情况都有案例分析+解决方案。
    misaka15
        2
    misaka15  
       2016-05-24 12:57:22 +08:00
    --.网上大部分教程都是这样的,希望加点创新的内容
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2650 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 12:12 · PVG 20:12 · LAX 04:12 · JFK 07:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.