本節(jié)分析NSURLSession內(nèi)存管理的三種情況:
- NSURLSession持有Block
- NSURLSession持有delegate
- NSURLSession會(huì)緩存HTTPS的認(rèn)證
1、2兩點(diǎn)最容易造成問題,3最容易讓人忽視。接下來就仔細(xì)探究一下這三種情況。
block
剛開始用session時(shí)使用的我們經(jīng)常使用如下代碼:
- (void)requestData {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// 此處為了下面分析內(nèi)存結(jié)構(gòu)方便,把block單獨(dú)分出來了
void (^block)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@",response);
NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
};
NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:block];
[dataTask resume];
}
這里有個(gè)問題,不知道大家用的時(shí)候不知道有沒有考慮過,session、dataTask以及block都是局部變量,當(dāng)方法執(zhí)行完成后應(yīng)該都銷毀了才是。但是請(qǐng)求回來時(shí)block是可以執(zhí)行的,那么肯定有一個(gè)對(duì)象持有所有上面的對(duì)象。那么:
- 是誰持有了上述對(duì)象?
- 何時(shí)釋放以上所有對(duì)象?
- 如果請(qǐng)求一直不返回是不是內(nèi)存泄漏?
我們以上述代碼為例,把斷點(diǎn)設(shè)在[dataTask resume]處,通過調(diào)試菜單和Debug Memory Graph來觀察請(qǐng)求發(fā)送前的內(nèi)存結(jié)構(gòu)。
調(diào)試菜單截圖如下:
可以看到,NSURLSessionTask是一個(gè)__NSCFLoaclDataTask私有類的實(shí)例對(duì)象。NSURLSession是__NSURLSessionLocal私有類的實(shí)例。NSURLSessionConfiguration是__NSCFURLSessionConfiguration對(duì)象,它是個(gè)單例。
下面就通過Debug Memory Graph來探究其內(nèi)存關(guān)系,由于Debug Memory Graph比較繁瑣,這里做了簡(jiǎn)化處理:
該圖顯示了Configuration的持有關(guān)系??梢钥吹?,它并沒被NSURLSession的對(duì)象持有,而是被加載系統(tǒng)持有。可以理解為NSURLSessionConfiguration是一個(gè)單例對(duì)象。
NSURLSession對(duì)象有個(gè)重要的特點(diǎn)就是被NSURLSessionDataTask對(duì)象持有。除此之外它還沒自動(dòng)釋放池和臨時(shí)堆棧持有。這兩個(gè)在方法執(zhí)行完后就會(huì)釋放。
NSURLSessionDataTask對(duì)象被block、自動(dòng)釋放池和堆棧。在方法執(zhí)行完成后,datatask就被block持有。
在請(qǐng)求發(fā)送后,這三者的持有關(guān)系基本沒變。NSURLSessionConfiguration還是個(gè)單例對(duì)象。但是URLProtocol替換了原來的autoreleasepool對(duì)象,NSURLSession對(duì)象被NSURLSessionTask持有,NSURLSessionTask被block持有。同時(shí)這兩個(gè)對(duì)象都被URLProtocol對(duì)象持有。
可以看到,configuration、session、datatask對(duì)象如果僅是局部變量時(shí),它們的生命周期是不需要我們管理的,由autoreleasepool和URLProtocol管理,我們也沒法插手。block應(yīng)該是由系統(tǒng)管理其內(nèi)存。
Delegate
delegate是由NSURLSession對(duì)象持有。
考慮一個(gè)問題:block可以在NSURLSessionTask收到信息后釋放掉,那么delegate該什么時(shí)候釋放?因?yàn)镹SURLSession上可以有多個(gè)task在運(yùn)行,想要釋放session需要等所有的task完成,此時(shí)必須調(diào)用finishTaskAndInvalidate或invalidateAndCancel方法來停止所有的task對(duì)象。這兩個(gè)方法的使用在NSURLSession的教程中說過。這也是為什么我們通常建立一個(gè)session單例來完成APP中絕大部分http請(qǐng)求。單例只創(chuàng)建一次也就沒有多次創(chuàng)建內(nèi)存泄漏的問題了。
SSL緩存
在蘋果的加載系統(tǒng)中存在一個(gè)SSL緩存問題。其詳細(xì)情況如下:在使用NSURLConnection進(jìn)行HTTPS后,會(huì)把TLS的內(nèi)存進(jìn)行緩存。第二次連接的時(shí)候就證書校驗(yàn)的回調(diào)可能就不會(huì)觸發(fā)。理由是TLS連接比較耗費(fèi)資源,所以系統(tǒng)對(duì)其做了緩存,緩存時(shí)間大約是9到11分鐘。緩存的key是目的ip、目的端口和目的DNS組成的,比如{131.39.46.209:47873}devforums.apple.com。所以,如果想繞開這個(gè)機(jī)制,請(qǐng)求的時(shí)候需要更改以上三個(gè)的任意一個(gè)即可。
如果NSURLSession該問題應(yīng)該不會(huì)出現(xiàn),因?yàn)槊總€(gè)NSURLSession都有其自己的TLS緩存空間,通常不會(huì)存在證書校驗(yàn)代理方法不觸發(fā)的情況。但是如果每個(gè)請(qǐng)求都使用另建一個(gè)NSURLSession也會(huì)造成資源的浪費(fèi)。同時(shí)還會(huì)造成內(nèi)存峰值的問題。