【iOS】獲取DNS地址的坑

寫在前面

如果你在百度或者google搜索過“ios獲取dns地址”之類的,那你一定中招了
關(guān)于網(wǎng)絡(luò)上的這段獲取DNS地址的代碼,我copy之后,也能工作。但是,我們牛逼的測試同學(xué),弄了個自動化,ε=(′ο`*)))唉,當(dāng)這段函數(shù)執(zhí)行幾百次之后,溢出的內(nèi)存就會慢慢長大,然后crash
如果要看結(jié)果,直接拉到最后,因為就改了一行代碼。費了老大勁才搞定的,當(dāng)然要寫詳細了

網(wǎng)上獲取DNS的核心代碼如下

- (NSArray *)outPutDNSServers { /// 獲取本機DNS服務(wù)器
    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的發(fā)現(xiàn)

上述代碼確實可以工作,然后我就沒有管
我做的項目里有個業(yè)務(wù)邏輯,當(dāng)用戶切換當(dāng)前設(shè)備的網(wǎng)絡(luò)之后,我會獲取對應(yīng)的DNS地址,用來做別的事情。吶,核心就是獲取DNS地址。然后測試搞了個自動化切換網(wǎng)絡(luò),時間久,進程沒有了。

由于我的進程是network Extension進程(memory 上限是15M,如下圖),和一般的APP進程還不一樣。最后加的很多打印才發(fā)現(xiàn)是進程crash了,我自己沒有自動化,依賴遠程的自動化同事。來來回回搞了一周才發(fā)現(xiàn)這個問題所在。


A3BA0C19-0A0D-4DAF-8407-495D9E34623A.png

分析

好了,現(xiàn)在知道是由什么原因造成的crash了,分析了影響面之后,放了一段時間。因為正常用戶不會連續(xù)來回切換網(wǎng)絡(luò)那么多次??們?nèi)存15M,進程正常占用4M,剩余11M被Leaks占滿,也挺不容易的。(后來發(fā)現(xiàn)每次獲取dns地址會溢出944bytes的內(nèi)存)

首先看下Instruments里L(fēng)eaks的表現(xiàn)

F3DE1D28-61DE-4D15-89A3-AA57FA04E22F.png

第一個框,表示泄漏了多少的內(nèi)存,以及占比,兩個紅框中間是發(fā)生的次數(shù),右側(cè)紅框是具體哪個函數(shù)導(dǎo)致的問題。至于怎么看到下面的這個圖,百度有很多教程

雙擊右側(cè)紅框中的outPutDNSServers函數(shù)
發(fā)現(xiàn)if (res_ninit(res) == 0) {這一行有問題,首先,不是if的問題,因為我試著單獨int temp = res_ninit(res) ;這樣寫過,然后雙擊函數(shù)的的時候,就定位到int temp = res_ninit(res) ;這一行了

嘗試解決

那就是res_ninit(res)這一行創(chuàng)建的內(nèi)存沒有被釋放。前面一行的res_state res = malloc(sizeof(struct __res_state));沒有事,是因為后面有個對應(yīng)的free(res);。

然后百度,沒有相關(guān)信息

然后Google,如下圖


截屏2020-05-27下午3.20.02.png

看來別的平臺的同學(xué)也有一樣的問題,那我就放心了。于是問同事

此時理應(yīng)(馬后炮)想到是res_nclose(res);沒有生效導(dǎo)致的,并沒有關(guān)閉這個內(nèi)存空間(且先不管是堆棧什么的),但是,同事給了我一個啟發(fā),要是我只res_nint一次,不要每次都創(chuàng)建新的,也不會溢出遞增了,溢出一次沒事,好,我就做成一個不會釋放的對象的屬性,這樣一直持有它,然后一頓測試。發(fā)現(xiàn)獲取到的dns地址并不會改變,我都手動切換網(wǎng)絡(luò)了,你還不變。。。。

然后我就新建工程,單獨手動執(zhí)行,Wow(虞書欣式)~如下圖,每次都是944bytes


截屏2020-05-27下午3.19.06.png

好,剛才的做成變量的方式不好使。
那就看那段代碼里的函數(shù),我一天了,就對著這代碼
實話實說,這個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總得對應(yīng)一個釋放函數(shù)吧,就好比malloc對應(yīng)free,OC中的retain對應(yīng)release一樣。那就看,全網(wǎng)用的是res_nclose,然后大佬說,可能沒對應(yīng)上,于是乎,換了個res_ndestroy試一下,果然好了。
我他嗎的,那么久,就這一行??!
完美解決?。?!

所以文中最開頭寫的那一段代碼應(yīng)該是這樣的

- (NSArray *)outPutDNSServers { /// 獲取本機DNS服務(wù)器
    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;
}

總結(jié)

這個過程是很痛苦的,也是很美好的,幸運有多個大佬幫助
中間對instruments找問題更加熟悉了,順帶改了幾個別的泄漏的問題
對于不認識的代碼,要勇敢的去猜
不分語言,你要空間,就要還空間,這是不變的

如果對resolv.h熟悉的朋友,我理解不對的,勞煩告訴我,一起進步

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,187評論 8 265
  • 近期整理的iOS面試題。不定期更新中。如有問題,歡迎斧正。 派發(fā) Swift 有三種派發(fā)方式 1靜態(tài)派發(fā) 2消息派...
    程序狗旭旭旭閱讀 1,938評論 0 5
  • 10、進程、線程、任務(wù)、隊列 一、 進程: 1.進程是一個具有一定獨立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運行活動,它...
    田小北北閱讀 506評論 0 1
  • Crash 日志從哪來?一般有 2 個渠道: 蘋果收集的 Crash 日志 用戶手機上 設(shè)置 -> 隱私 -> 分...
    M_慕宸閱讀 2,911評論 0 10
  • final修飾的變量會指向一塊固定的內(nèi)存, 這塊內(nèi)存中的值不能改變. 存儲過程 禁止使用存儲過程,存儲過程難以調(diào)試...
    lconcise閱讀 1,022評論 0 1

友情鏈接更多精彩內(nèi)容