写在前面
如果你在百度或者google搜索过“ios获取dns地址”之类的,那你一定中招了 关于网络上的这段获取DNS地址的代码,我copy之后,也能工作。但是,我们牛逼的测试同学,弄了个自动化,ε=(´ο`*)))唉,当这段函数执行几百次之后,溢出的内存就会慢慢长大,然后crash 如果要看结果,直接拉到最后,因为就改了一行代码。费了老大劲才搞定的,当然要写详细了
网上获取DNS的核心代码如下
- (NSArray *)outPutDNSServers { /// 获取本机DNS服务器
res_state res = malloc(sizeof(struct __res_state));
NSMutableArray *dnsArray = [NSMutableArray new];
if (res_ninit(res) == 0) {
for (int i = 0; i < res->nscount; i++ ) {
NSString *s = [NSString stringWithUTF8String:inet_ntoa(res->nsaddr_list[i].sin_addr)];
[dnsArray addObject:s];
}
}
res_ndestroy(res);
free(res);
return dnsArray.mutableCopy;
}
crash的发现
上述代码确实可以工作,然后我就没有管 我做的项目里有个业务逻辑,当用户切换当前设备的网络之后,我会获取对应的DNS地址,用来做别的事情。呐,核心就是获取DNS地址。然后测试搞了个自动化切换网络,时间久,进程没有了。
由于我的进程是network Extension进程(memory 上限是15M,如下图),和一般的APP进程还不一样。最后加的很多打印才发现是进程crash了,我自己没有自动化,依赖远程的自动化同事。来来回回搞了一周才发现这个问题所在。
###分析 好了,现在知道是由什么原因造成的crash了,分析了影响面之后,放了一段时间。因为正常用户不会连续来回切换网络那么多次。总内存15M,进程正常占用4M,剩余11M被Leaks占满,也挺不容易的。(后来发现每次获取dns地址会溢出944bytes的内存)
首先看下Instruments里Leaks的表现
第一个框,表示泄漏了多少的内存,以及占比,两个红框中间是发生的次数,右侧红框是具体哪个函数导致的问题。至于怎么看到下面的这个图,百度有很多教程
双击右侧红框中的outPutDNSServers
函数
发现if (res_ninit(res) == 0) {
这一行有问题,首先,不是if
的问题,因为我试着单独int temp = res_ninit(res) ;
这样写过,然后双击函数的的时候,就定位到int temp = res_ninit(res) ;
这一行了
###尝试解决
那就是res_ninit(res)
这一行创建的内存没有被释放。前面一行的res_state res = malloc(sizeof(struct __res_state));
没有事,是因为后面有个对应的free(res);
。
然后百度,没有相关信息
然后Google,如下图
看来别的平台的同学也有一样的问题,那我就放心了。于是问同事
此时理应(马后炮)想到是res_nclose(res);
没有生效导致的,并没有关闭这个内存空间(且先不管是堆栈什么的),但是
,同事给了我一个启发,要是我只res_nint一次,不要每次都创建新的,也不会溢出递增了,溢出一次没事,好,我就做成一个不会释放的对象的属性,这样一直持有它,然后一顿测试。发现获取到的dns地址并不会改变,我都手动切换网络了,你还不变。。。。
然后我就新建工程,单独手动执行,Wow(虞书欣式)~如下图,每次都是944bytes
好,刚才的做成变量的方式不好使。
那就看那段代码里的函数,我一天了,就对着这代码
实话实说,这个resolv.lib里的东西也不熟,res_ninit(res)
点进去是这样的
//resolv.h
#define res_dnok res_9_dnok
#define res_findzonecut res_9_findzonecut
#define res_findzonecut2 res_9_findzonecut2
#define res_hnok res_9_hnok
#define res_hostalias res_9_hostalias_2
#define res_mailok res_9_mailok
#define res_nameinquery res_9_nameinquery
#define res_nclose res_9_nclose
#define res_ninit res_9_ninit
#define res_nmkquery res_9_nmkquery
#define res_pquery res_9_pquery
#define res_nquery res_9_nquery
#define res_nquerydomain res_9_nquerydomain
#define res_nsearch res_9_nsearch
#define res_nsend res_9_nsend
#define res_nsendsigned res_9_nsendsigned
#define res_nisourserver res_9_nisourserver
#define res_ownok res_9_ownok
#define res_queriesmatch res_9_queriesmatch
#define res_randomid res_9_randomid
#define sym_ntop res_9_sym_ntop
#define sym_ntos res_9_sym_ntos
#define sym_ston res_9_sym_ston
#define res_nopt res_9_nopt
#define res_ndestroy res_9_ndestroy
#define res_nametoclass res_9_nametoclass
#define res_nametotype res_9_nametotype
#define res_setservers res_9_setservers
#define res_getservers res_9_getservers
通过猜测res_ninit
总得对应一个释放函数吧,就好比malloc
对应free
,OC中的retain
对应release
一样。那就看,全网用的是res_nclose
,然后大佬说,可能没对应上,于是乎,换了个res_ndestroy
试一下,果然好了。
我他吗的,那么久,就这一行!!
完美解决!!!
所以文中最开头写的那一段代码应该是这样的
- (NSArray *)outPutDNSServers { /// 获取本机DNS服务器
res_state res = malloc(sizeof(struct __res_state));
NSMutableArray *dnsArray = [NSMutableArray new];
if (res_ninit(res) == 0) {
for (int i = 0; i < res->nscount; i++ ) {
NSString *s = [NSString stringWithUTF8String:inet_ntoa(res->nsaddr_list[i].sin_addr)];
[dnsArray addObject:s];
}
}
res_ndestroy(res);//!!改了这里
free(res);
return dnsArray.mutableCopy;
}
总结
这个过程是很痛苦的,也是很美好的,幸运有多个大佬帮助 中间对instruments找问题更加熟悉了,顺带改了几个别的泄漏的问题 对于不认识的代码,要勇敢的去猜 不分语言,你要空间,就要还空间,这是不变的
如果对resolv.h熟悉的朋友,我理解不对的,劳烦告诉我,一起进步