记录一次内存泄漏排查历程
背景
通过在deinit方法内打印日志,发现某个ViewController的deinit方法没有执行,决定排查下具体的原因。
方案1,instruments排查
打开instruments,按照内存泄漏的流程进行检测,反复进入,退出目标ViewController,检查instruments法相并没有定位到内存泄漏的代码,为了保险起见,又参考了网上的检测流程反复多次进行检测,结果都是一样没有定位到内存泄漏的具体代码。
方案2:Xcode Analysis
使用Xcode的Analysis 功能确实分析出来了一些内存泄漏的代码,但是和目标ViewController没有关系。
方案3, 代码注释法
通过对目标ViewController的代码不断进行注释,检查deinit方法是否会执行,不断缩小范围,最终定位到一行代码,如下:
IZoneInterfaceiOS.sharedInstance()?.traceHandler = self
traceHandler的定义如下:
@property(weak, nonatomic) id<IZoneTraceCb> traceHandler;
根据目前的信息看,一个被声明为weak的属性,不可能还会出现被持有不释放造成内存泄漏的情况。
继续排查使用的地方,发现只有一个地方进行了使用,代码如下:
void sdk_log_cb(SdkLogLevel level, const char* msg)
{
NSString* log_msg = [NSString stringWithUTF8String:msg];
if ([IZoneInterfaceiOS sharedInstance].traceHandler)
{
[[IZoneInterfaceiOS sharedInstance].traceHandler onTrace:(ZoneLogLevel)level log:log_msg];
}
}
将使用到traceHandler的代码注释掉,发现内存泄漏不存在了,注释掉以后的代码如下:
void sdk_log_cb(SdkLogLevel level, const char* msg)
{
NSString* log_msg = [NSString stringWithUTF8String:msg];
// if ([IZoneInterfaceiOS sharedInstance].traceHandler)
//{
// [[IZoneInterfaceiOS sharedInstance].traceHandler onTrace:(ZoneLogLevel)level log:log_msg];
// }
}
然后把只注释掉如下代码:
void sdk_log_cb(SdkLogLevel level, const char* msg)
{
NSString* log_msg = [NSString stringWithUTF8String:msg];
if ([IZoneInterfaceiOS sharedInstance].traceHandler)
{
// [[IZoneInterfaceiOS sharedInstance].traceHandler onTrace:(ZoneLogLevel)level log:log_msg];
}
}
分析可知,只要是在这个函数里用了traceHandler,就会产生内存泄漏,及时是仅仅打印traceHandler也会泄漏。后来验证了一下,确实如此。
仔细思考了发现这个函数是C,C++的函数,在执行的时候会对traceHandler默认执行retain操作,但是这个traceHandler是OC对象,释放需要OC来执行release操作,,但是ARC下已经不能手动执行release操作了。决定把这段代码放到自动释放池中,当执行完后,自动执行一下release操作。验证后发现确实如此,也解决了内存泄漏。修改后的代码如下:
void sdk_log_cb(SdkLogLevel level, const char* msg)
{
NSString* log_msg = [NSString stringWithUTF8String:msg];
@autoreleasepool {
if ([IZoneInterfaceiOS sharedInstance].traceHandler)
{
[[IZoneInterfaceiOS sharedInstance].traceHandler onTrace:(ZoneLogLevel)level log:log_msg];
}
}
}