原文鏈接:http://lm1024.xyz/2020/05/31/NSURLSession-memory-leak/
場景
在使用 profile查找項(xiàng)目中的內(nèi)存泄露問題時(shí)發(fā)現(xiàn)在上傳文件的模塊中總是報(bào)內(nèi)存泄露。自己封裝的上傳器工具類也沒走 dealloc。Profile 只提示了我開始上傳的方法里面存在泄露。
內(nèi)存泄露的情況大概有
- block 循環(huán)引用
- delegate 強(qiáng)引用
- 自定義對(duì)象之間互相持有
- 系統(tǒng)對(duì)象和自定義對(duì)象之間互相持有
- CoreFoundation 框架對(duì)象沒有手動(dòng) Realese 掉
分析問題
經(jīng)初步分析可能會(huì)有以下兩個(gè)情況會(huì)導(dǎo)致 上傳工具類不被釋放
- 上傳完成之后上傳管理器中的
文件 URL<-->上傳工具類之間的映射 map 沒有將上傳工具對(duì)象從 map 里面移除掉。 - block 造成循環(huán)引用
按照上述的思路修改之后再次 profile 文件上傳模塊發(fā)現(xiàn)熟悉的內(nèi)存泄露還是出現(xiàn)了??吹竭@里笑容漸漸消失了。確定了按上述思路修改沒有遺漏的地方后我意識(shí)到可能是上傳工具類內(nèi)部處問題。
#pragma Setter / Getter
- (NSURLSession *)uploadSession
{
if (_uploadSession == nil)
{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_uploadSession = [NSURLSession sessionWithConfiguration:config
delegate:self delegateQueue:self.runQueue];
}
return _uploadSession;
}
檢查到上面代碼的時(shí)候發(fā)現(xiàn)一個(gè)可疑點(diǎn)[NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:self.runQueue];
在給NSURLSession 設(shè)置 delegate 的時(shí)候,NSURLSession 會(huì)不會(huì)是對(duì) delegate 進(jìn)行了強(qiáng)引用?帶著疑問去查了下文檔
- configuration
A configuration object that specifies certain behaviors, such as caching policies, timeouts, proxies, pipelining, TLS versions to support, cookie policies, and credential storage.
See NSURLSessionConfiguration for more information.- delegate
A session delegate object that handles requests for authentication and other session-related events.
This delegate object is responsible for handling authentication challenges, for making caching decisions, and for handling other session-related events. If nil, the class should be used only with methods that take completion handlers.
Important
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session by calling the invalidateAndCancel or finishTasksAndInvalidate method, your app leaks memory until it exits.- queue
An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
果然,沒有猜錯(cuò), NSURLSession對(duì)象強(qiáng)引用了我的上傳工具類并且 NSURLSession對(duì)象又是上傳工具類的一個(gè)屬性。這樣就形成了循環(huán)引用,這種情況屬于
- 系統(tǒng)對(duì)象強(qiáng)制持有 delegate 類
解決方案
破除循環(huán)引用的方法文檔中也給出來了在下載完成時(shí)調(diào)用- (void)finishTasksAndInvalidate;, 取消當(dāng)前會(huì)話中task任務(wù)并且是當(dāng)前會(huì)話失效 調(diào)用- (void)invalidateAndCancel;。
- (void)uploadData:(NSData *)data completed:(WLFileUploadCompleted) completed
{
self.uploadTask = [self.uploadSession uploadTaskWithRequest:self.request
fromData:data
completionHandler:completed];
[self.uploadTask addObserver:self
forKeyPath:@"state"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:@"state"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSString * cntext = (__bridge NSString *)context;
if ([cntext isEqualToString:@"state"] && [keyPath isEqualToString:@"state"])
{
NSURLSessionTaskState state = [change[NSKeyValueChangeNewKey] integerValue];
NSLog(@"NEW NSURLSessionTaskState = %ld ",(long)state);
if (state == NSURLSessionTaskStateCanceling ||
state == NSURLSessionTaskStateCompleted)
{
[self releaseCallback];
}
}
}
- (void)releaseCallback
{
self.progress = nil;
[self.uploadTask removeObserver:self forKeyPath:@"state"];
if (self.state == NSURLSessionTaskStateCompleted)
{
[self.uploadSession finishTasksAndInvalidate];
}
if (self.state == NSURLSessionTaskStateCanceling)
{
[self.uploadSession invalidateAndCancel];
}
}
通過添加上面三個(gè)方法 再次 profile文件上傳模塊發(fā)現(xiàn)內(nèi)存泄露消失了。