【iOS】获取DNS地址的坑

99 阅读4分钟

写在前面

如果你在百度或者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了,我自己没有自动化,依赖远程的自动化同事。来来回回搞了一周才发现这个问题所在。 A3BA0C19-0A0D-4DAF-8407-495D9E34623A.png

###分析 好了,现在知道是由什么原因造成的crash了,分析了影响面之后,放了一段时间。因为正常用户不会连续来回切换网络那么多次。总内存15M,进程正常占用4M,剩余11M被Leaks占满,也挺不容易的。(后来发现每次获取dns地址会溢出944bytes的内存)

首先看下Instruments里Leaks的表现 F3DE1D28-61DE-4D15-89A3-AA57FA04E22F.png 第一个框,表示泄漏了多少的内存,以及占比,两个红框中间是发生的次数,右侧红框是具体哪个函数导致的问题。至于怎么看到下面的这个图,百度有很多教程

双击右侧红框中的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,如下图 截屏2020-05-27下午3.20.02.png 看来别的平台的同学也有一样的问题,那我就放心了。于是问同事

此时理应(马后炮)想到是res_nclose(res);没有生效导致的,并没有关闭这个内存空间(且先不管是堆栈什么的),但是,同事给了我一个启发,要是我只res_nint一次,不要每次都创建新的,也不会溢出递增了,溢出一次没事,好,我就做成一个不会释放的对象的属性,这样一直持有它,然后一顿测试。发现获取到的dns地址并不会改变,我都手动切换网络了,你还不变。。。。

然后我就新建工程,单独手动执行,Wow(虞书欣式)~如下图,每次都是944bytes 截屏2020-05-27下午3.19.06.png

好,刚才的做成变量的方式不好使。 那就看那段代码里的函数,我一天了,就对着这代码 实话实说,这个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熟悉的朋友,我理解不对的,劳烦告诉我,一起进步