问题:对象释放时机,是立即释放吗
在调用 release 后,对象会被立即释放,而调用 autorelease 后,对象不会被立即释放,而是注册到 autoreleasepool 中,经过一段时间后 pool结束,此时调用release方法,对象被释放。
问题:内存泄漏
内存泄漏含义
内存泄漏:是指申请的内存空间使用完毕之后未回收。一次内存泄露危害可以忽略,但若一直泄漏,无论有多少内存,迟早都会被占用光,最终导致程序crash。
内存溢出:是指程序在申请内存时,没有足够的内存空间供其使用。 通俗理解就是内存不够用了,通常在运行大型应用或游戏时,应用或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。最终导致机器重启或者程序crash。
内存泄漏检测
第一种:静态分析方法(Analyze)
第二种:动态分析方法(Instrument工具库里的Leaks)。一般推荐使用第二种。
静态分析方法(Analyze)
1.第一步:通过Xcode打开项目,然后点击Product->Analyze,开始进入静态内存泄漏分析。 如下图所示:(不需运行程序)
1612594274648.jpg
第二步:等待分析结果。
第三步:根据分析的结果对可能造成内存泄漏的代码进行排查。
PS:静态内存泄漏分析能发现大部分问题,但只是静态分析,并且并不准确,只是有可能发生内存泄漏。一些动态内存分配的情形并没有分析。如果需要更精准一些,那就要用到下面要介绍的动态内存泄漏分析方法(Instruments工具中的Leaks方法)进行排查。
动态分析方法(Instrument工具库里的Leaks)
1.第一步:通过Xcode打开项目,然后点击Product->Profile
2.按上面操作,build成功后跳出Instruments工具,如上图右侧图所示。选择Leaks选项,点击右下角的【choose】按钮。
1612594948315.jpg
第三步:这时候项目程序也在模拟器或手机上运行起来了,在手机或模拟器上对程序进行操作,工具显示效果如下:
1612595001604.jpg
点击左上角的红色圆点,这时项目开始启动了,由于Leaks是动态监测,所以手动进行一系列操作,可检查项目中是否存在内存泄漏问题。如图所示,橙色矩形框中所示绿色为正常,如果出现如右侧红色矩形框中显示红色,则表示出现内存泄漏。 1612595056378.jpg
选中Leaks Checks,在Details所在栏中选择CallTree,并且在右下角勾选Invert Call Tree 和Hide System Libraries,会发现显示若干行代码,双击即可跳转到出现内存泄漏的地方,修改即可。
举个例子:
1612595106253.jpg
内存泄漏原因分析
目前,在ARC环境下,导致内存泄漏的根本原因是代码中存在循环引用,从而导致一些内存无法释放,最终导致dealloc()方法无法被调用。主要原因大概有一下几种类型:
1.ViewController中存在NSTimer
2.ViewController中的代理delegate
3.ViewController中Block
iOS常见内存问题及优化
内存泄漏
ARC 模式下,开发者不再需要手动释放内存,所有内存泄漏基本都是由于对象循环引用引起的。优化方案就是避免循环引用。
WKWebView 白屏问题
UIWebView 会因为内存使用过大而崩溃,WKWebView 苹果进行了优化,不会 Crash 但会导致白屏,不显示内容。
解决方法是监听到 URL 为 nil 或者接收到 WKNavigationDelegate 的 webViewWebContentProcessDidTerminate 时,reload 页面。
野指针
目前最为常见的野指针是 objc_msgSend 和 unrecognized selector sent to,只要能记录崩溃时的调用栈,一般都较容易解决。
开发阶段可以通过开启编译里的 Zombie Objects 复现问题,原理是 Hook 系统的 dealloc 方法,执行 __dealloc_zombie 将对象进行僵尸化,如果当前对象再次收到消息,则终止程序并打印出调用信息。
图片内存
图片读取
imageNamed 会被缓存到内存中,适用于频繁使用的小图片;imageWithContentOfFile 适用于大图片,持有者生命周期结束后既被释放。
图片格式
iOS 默认创建的图片格式是 SRGB,每个像素点通常包括红、绿、蓝和 alpha 数据4个字节。而实际使用时,图像可能不需要这么多通道。
使用 UIGraphicsBeginImageContextWithOptions 创建的格式固定是 SRGB,可以使用 UIGraphicsImageRenderer (iOS10之后)替代,会自动选择最合适的图像格式。
缩放图像
将大图片加载到小空间时, UIImage (UIImage.contentsOfFile)需要先解压整个图像再渲染,会产生内存峰值,用 ImageIO框架 替代 UIImage 可避免图像峰值,ImageIO框架(CGImageSourceCreateWithURL)可以直接指定加载到内存的图像尺寸和信息,省去了解压缩的过程。
后台优化
当应用切入后台时,图像默认还在内存中 ,可以在退到后台或view消失时从内存中移除图片,进入前台或view出现时再加载图片 (通过监听系统通知) 。
HEIC 格式
HEIC 是苹果推出的专门用于其系统的图片格式,iOS 11以上支持。
据测试,相同画质比 JPEG 节省 50% 内存,且支持保存辅助图片(深度图、视差图等)。
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微信内存监控。
-
其它优化
- 构建缓存时使用 NSCache 替代 NSMutableDictionary
NSCache 是线程安全的,当内存不足时会自动释放内存(取数据时需要先判空),并且可以通过 countLimit 和 totalCostLimit 属性设置上限,另外对存在 Compressed Memory 情况下的内存警告也做了优化,这些都是 NSDictionary 不具备的。
- 不要将序列化的数据文件当作数据库使用‘
- Plists、XML、JSON等文件修改都必须替换整个文件,拓展性差,且开销大,容易误用
- NSUserDefaults默认是Plist
问题:Core Foundation内存管理
ARC 能够解决 iOS 开发中大多数的内存管理问题,但是还有少量一些内存管理,是需要开发者自己处理的,这主要就是与底层 Core Foundation 对象交互的那部分,底层的 Core Foundation 对象由于不在 ARC 的管理下,所以需要自己维护这些对象的引用计数。
底层的 Core Foundation 对象,在创建时大多以 XxxCreateWithXxx 这样的方式创建,例如:
// 创建一个 CFStringRef 对象
CFStringRef str= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8);
// 创建一个 CTFontRef 对象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
对于这些对象的引用计数的修改,要相应的使用 CFRetain 和 CFRelease 方法。如下所示:
// 创建一个 CTFontRef 对象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
// 引用计数加 1
CFRetain(fontRef);
// 引用计数减 1
CFRelease(fontRef);
对于 CFRetain 和 CFRelease 两个方法,读者可以直观地认为,这与 Objective-C 对象的 retain 和 release 方法等价。
所以对于底层 Core Foundation 对象,我们只需要延续以前手工管理引用计数的办法即可。
除此之外,还有另外一个问题需要解决。在 ARC 下,我们有时需要将一个 Core Foundation 对象转换成一个 Objective-C 对象,这个时候我们需要告诉编译器,转换过程中的引用计数需要做如何的调整。这就引入了bridge相关的关键字,以下是这些关键字的说明:
__bridge: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
__bridge_retained:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
__bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。
我们根据具体的业务逻辑,合理使用上面的 3 种转换关键字,就可以解决 Core Foundation 对象与 Objective-C 对象相对转换的问题了。
问题:alloc/init和New区别
第一种方式(alloc init)来创建对象时,系统首先会给变量分配内存,然后调用init方法来进行初始化,或者调用initWith方法来初始化。
第二种方式(new)是第一种方式中两步的综合,系统会直接开辟好内存,调用init方法来初始化对象,但是只能调用init方法。
问题:空指针、悬垂指针和野指针
空指针:指针指向的地址为空的指针叫空指针(NULL指针)
野指针:是指向“垃圾”内存(不可用内存)的指针
产生原因:指针创建时未初始化。指针变量刚被创建时不会自动成为NULL指针,它会随机指向一个内存地址。
悬垂指针:指针所指向的对象已经被释放或者回收了,但是指向该对象的指针没有作任何的修改,仍旧指向已经回收的内存地址。 此类指针称为垂悬指针。
问题:assign能修饰对象吗
使用weak和assign修饰OC对象的区别
1.weak生成的成员变量是用__weak修饰的,比如Cat * __weak _cat;
- assign生成的成员变量是用__unsafe_unretained修饰的Cat * __unsafe_unretained _cat;
网友评论