iOS性能---内存优化

为什么要优化内存?

  • Jetsam 机制下,系统从内核中开启了最高优先级的线程,来监控整个系统的内存情况。当系统内存不足时,会自动发出内存警告或触发 OOM 杀死低优先级的进程,腾出内存供其他高优先级进程使用。

  • MacOS / iOS是一个从 BSD 衍生而来的系统,内核是 XNU,XNU 的微内核是 Mach,其处理内存警告和异常使用的是 Jetsam 机制。

  • 内存警告三种方式

    • UIApplicationDelegate的 applicationDidReceiveMemoryWarning:
    • UIViewController的 didReceiveMemoryWarning
    • NSNotificationCenter的 UIApplicationDidReceiveMemoryWarningNotification
  • 三种内存类型

    • Clean Memory 是指那些可以用以 Page Out(当内存不足的时候,系统会按照一定策略来腾出更多空间供使用,比较常见的做法是将一部分低优先级的数据挪到磁盘上) 的内存。

    • Dirty Memory 是指那些被 App 写入过数据的内存。

    • Compressed Memory 当内存吃紧的时候,系统会将不使用的内存进行压缩,直到下一次访问的时候进行解压。

OOM 监控

  • 指 App 在前台因消耗内存过大导致被系统杀死,针对这类问题,我们需要记录发生 FOOM 时的调用栈、内存占用等信息,从而具体分析解决内存占用大的问题。

  • 流程是监控 App 生命周期内的内存增减,在收到内存警告时,记录内存信息,获取当前所有对象信息和内存占用值,并在合适的时机上传到服务器。目前比较出名的 OOM 监控框架有 Facebook 的 FBAllocationTracker ,国内的有腾讯开源的** OOMDetector**。

    • FBAllocationTracker
      原理是 hookmalloc/free 等方法,以此在运行时记录所有实例的分配信息,从而发现一些实例的内存异常情况,有点类似于在 app 内运行、性能更好的 Allocation。但是这个库只能监控 Objective-C 对象,所以局限性非常大,同时因为没办法拿到对象的堆栈信息,所以更难定位 OOM 的具体原因。

    • OOMDetector
      通过 malloc/free 的更底层接口 malloc_logger_t 记录当前存活对象的内存分配信息,同时也根据系统的 backtrace_symbols 回溯了堆栈信息。之后再根据伸展树(Splay Tree)等做数据存储分析,具体方式参看这篇文章:iOS微信内存监控

iOS常见内存问题及优化

  1. 内存泄漏

    • 检测
      Analyze静态分析
      1
      2
      3
      4
      内存泄露检查 Memory Error
      逻辑错误检查 Logic Error
      声明错误检查 Dead Store
      API调用错误检查 API Misuse
      MLeaksFinder
      1
      2
      3
      // 原理:在NSObject分类中添加willDealloc方法,2秒钟后执行assertNotDealloc,如果对象已经释放不会再执行
      // pop 在UINavigationController+MemoryLeak交换pop和push方法,用关联对象记录是不是执行willDealloc,然后在 UIViewController+MemoryLeak 交换viewDidDisappear,判断关联对象记录然后执行willDealloc
      // dismiss 在 UIViewController+MemoryLeak 交换dismissViewController 然后直接执行 willDealloc
      Instruments中的Leak动态分析内存泄漏
    • 一般原因
      Block循环引用
      NSTimer循环引用
      CoreFoundation方式申请的内存,忘记释放
  2. WKWebView 白屏问题

    • UIWebView 会因为内存使用过大而崩溃,WKWebView 苹果进行了优化,不会 Crash 但会导致白屏,不显示内容。

    • 解决方法是监听到 URL 为 nil 或者接收到 WKNavigationDelegate 的 webViewWebContentProcessDidTerminate 时,reload 页面。

  3. 野指针

    • 目前最为常见的野指针是 objc_msgSend 和 unrecognized selector sent to,只要能记录崩溃时的调用栈,一般都较容易解决。

    • 开发阶段可以通过开启编译里的 Zombie Objects 复现问题,原理是 Hook 系统的 dealloc 方法,执行 __dealloc_zombie 将对象进行僵尸化,如果当前对象再次收到消息,则终止程序并打印出调用信息。

  4. 图片内存

    • 图片读取
      imageNamed 会被缓存到内存中,适用于频繁使用的小图片;imageWithContentOfFile 适用于大图片,持有者生命周期结束后既被释放。
    • 图像压缩
      将大图片加载到小空间时, UIImage (UIImage.contentsOfFile)需要先解压整个图像再渲染,会产生内存峰值,用 ImageIO框架 替代 UIImage 可避免图像峰值,ImageIO框架(CGImageSourceCreateWithURL)可以直接指定加载到内存的图像尺寸和信息,省去了解压缩的过程
  5. autoreleasepool降低内存峰值

    • 通常autoreleased对象是在runloop结束时才释放。如果在循环里产生autoreleased对象,内存峰值会猛涨,甚至出现OOM。适当的添加autoreleasepool能及时释放内存,降低峰值。

参考文章

iOS微信内存监控
深入探索 iOS 内存优化