寫在前面
如果你在百度或者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)這個問題所在。

分析
好了,現(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)

第一個框,表示泄漏了多少的內(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,如下圖

看來別的平臺的同學(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

好,剛才的做成變量的方式不好使。
那就看那段代碼里的函數(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熟悉的朋友,我理解不對的,勞煩告訴我,一起進步