为什么要优化内存?
-
Jetsam机制下,系统从内核中开启了最高优先级的线程,来监控整个系统的内存情况。当系统内存不足时,会自动发出内存警告或触发OOM杀死低优先级的进程,腾出内存供其他高优先级进程使用。 -
MacOS / iOS是一个从 BSD 衍生而来的系统,内核是 XNU,XNU 的微内核是
Mach,其处理内存警告和异常使用的是Jetsam机制。 -
内存警告三种方式
UIApplicationDelegate的 applicationDidReceiveMemoryWarning:UIViewController的 didReceiveMemoryWarningNSNotificationCenter的 UIApplicationDidReceiveMemoryWarningNotification
-
三种内存类型
-
Clean Memory是指那些可以用以 Page Out(当内存不足的时候,系统会按照一定策略来腾出更多空间供使用,比较常见的做法是将一部分低优先级的数据挪到磁盘上) 的内存。 -
Dirty Memory是指那些被 App 写入过数据的内存。 -
Compressed Memory当内存吃紧的时候,系统会将不使用的内存进行压缩,直到下一次访问的时候进行解压。
-
OOM 监控
-
指 App 在前台因消耗内存过大导致被系统杀死,针对这类问题,我们需要记录发生 FOOM 时的调用栈、内存占用等信息,从而具体分析解决内存占用大的问题。
-
流程是监控 App 生命周期内的内存增减,在收到内存警告时,记录内存信息,获取当前所有对象信息和内存占用值,并在合适的时机上传到服务器。目前比较出名的 OOM 监控框架有 Facebook 的 FBAllocationTracker ,国内的有腾讯开源的** OOMDetector**。
-
FBAllocationTracker
原理是hook了malloc/free等方法,以此在运行时记录所有实例的分配信息,从而发现一些实例的内存异常情况,有点类似于在 app 内运行、性能更好的 Allocation。但是这个库只能监控Objective-C对象,所以局限性非常大,同时因为没办法拿到对象的堆栈信息,所以更难定位OOM的具体原因。 -
OOMDetector
通过malloc/free的更底层接口malloc_logger_t记录当前存活对象的内存分配信息,同时也根据系统的backtrace_symbols回溯了堆栈信息。之后再根据伸展树(Splay Tree)等做数据存储分析,具体方式参看这篇文章:iOS微信内存监控。
-
iOS常见内存问题及优化
-
内存泄漏
- 检测
Analyze静态分析MLeaksFinder1
2
3
4内存泄露检查 Memory Error
逻辑错误检查 Logic Error
声明错误检查 Dead Store
API调用错误检查 API MisuseInstruments中的Leak动态分析内存泄漏1
2
3// 原理:在NSObject分类中添加willDealloc方法,2秒钟后执行assertNotDealloc,如果对象已经释放不会再执行
// pop 在UINavigationController+MemoryLeak交换pop和push方法,用关联对象记录是不是执行willDealloc,然后在 UIViewController+MemoryLeak 交换viewDidDisappear,判断关联对象记录然后执行willDealloc
// dismiss 在 UIViewController+MemoryLeak 交换dismissViewController 然后直接执行 willDealloc - 一般原因
Block循环引用
NSTimer循环引用
CoreFoundation方式申请的内存,忘记释放
- 检测
-
WKWebView 白屏问题
-
UIWebView 会因为内存使用过大而崩溃,WKWebView 苹果进行了优化,不会 Crash 但会导致白屏,不显示内容。
-
解决方法是监听到 URL 为 nil 或者接收到 WKNavigationDelegate 的 webViewWebContentProcessDidTerminate 时,reload 页面。
-
-
野指针
-
目前最为常见的野指针是 objc_msgSend 和 unrecognized selector sent to,只要能记录崩溃时的调用栈,一般都较容易解决。
-
开发阶段可以通过开启编译里的 Zombie Objects 复现问题,原理是 Hook 系统的 dealloc 方法,执行 __dealloc_zombie 将对象进行僵尸化,如果当前对象再次收到消息,则终止程序并打印出调用信息。
-
-
图片内存
- 图片读取
imageNamed 会被缓存到内存中,适用于频繁使用的小图片;imageWithContentOfFile 适用于大图片,持有者生命周期结束后既被释放。 - 图像压缩
将大图片加载到小空间时, UIImage (UIImage.contentsOfFile)需要先解压整个图像再渲染,会产生内存峰值,用 ImageIO框架 替代 UIImage 可避免图像峰值,ImageIO框架(CGImageSourceCreateWithURL)可以直接指定加载到内存的图像尺寸和信息,省去了解压缩的过程
- 图片读取
-
autoreleasepool降低内存峰值
- 通常autoreleased对象是在runloop结束时才释放。如果在循环里产生autoreleased对象,内存峰值会猛涨,甚至出现OOM。适当的添加autoreleasepool能及时释放内存,降低峰值。