【iOS】获取DNS地址的坑

写在前面

如果你在百度或者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熟悉的朋友,我理解不对的,劳烦告诉我,一起进步

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 30,011评论 8 265
  • 近期整理的iOS面试题。不定期更新中。如有问题,欢迎斧正。 派发 Swift 有三种派发方式 1静态派发 2消息派...
    程序狗旭旭旭阅读 5,728评论 0 4
  • 10、进程、线程、任务、队列 一、 进程: 1.进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它...
    田小北北阅读 3,255评论 0 1
  • Crash 日志从哪来?一般有 2 个渠道: 苹果收集的 Crash 日志 用户手机上 设置 -> 隐私 -> 分...
    M_慕宸阅读 7,811评论 0 10
  • final修饰的变量会指向一块固定的内存, 这块内存中的值不能改变. 存储过程 禁止使用存储过程,存储过程难以调试...
    lconcise阅读 4,509评论 0 1

友情链接更多精彩内容