我正在构建一个使用网络服务的应用,并从我使用NSURLSession
和NSURLSessionDataTask
的网络服务中获取信息。
我现在处于内存测试阶段,我发现NSURLSession
导致内存泄漏。
这不是所有泄漏。这就是我能够适应的情况。
以下是我如何设置NSURLSession
并从网络服务请求信息。
#pragma mark - Getter Methods
- (NSURLSessionConfiguration *)sessionConfiguration
{
if (_sessionConfiguration == nil)
{
_sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
[_sessionConfiguration setHTTPAdditionalHeaders:@{@"Accept": @"application/json"}];
_sessionConfiguration.timeoutIntervalForRequest = 60.0;
_sessionConfiguration.timeoutIntervalForResource = 120.0;
_sessionConfiguration.HTTPMaximumConnectionsPerHost = 1;
}
return _sessionConfiguration;
}
- (NSURLSession *)session
{
if (_session == nil)
{
_session = [NSURLSession
sessionWithConfiguration:self.sessionConfiguration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
#pragma mark -
#pragma mark - Data Task
- (void)photoDataTaskWithRequest:(NSURLRequest *)theRequest
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Photo Request Data Task Set");
#endif
// Remove existing data, if any
if (_photoData)
{
[self setPhotoData:nil];
}
self.photoDataTask = [self.session dataTaskWithRequest:theRequest];
[self.photoDataTask resume];
}
#pragma mark -
#pragma mark - Session
- (void)beginPhotoRequestWithReference:(NSString *)aReference
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Fetching Photo Data...");
#endif
_photoReference = aReference;
NSString * serviceURLString = [[NSString alloc] initWithFormat:@"%@/json?photoreference=%@", PhotoRequestBaseAPIURL, self.photoReference];
NSString * encodedServiceURLString = [serviceURLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
serviceURLString = nil;
NSURL * serviceURL = [[NSURL alloc] initWithString:encodedServiceURLString];
encodedServiceURLString = nil;
NSURLRequest * request = [[NSURLRequest alloc] initWithURL:serviceURL];
[self photoDataTaskWithRequest:request];
serviceURL = nil;
request = nil;
}
- (void)cleanupSession
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Cleaned Up");
#endif
[self setPhotoData:nil];
[self setPhotoDataTask:nil];
[self setSession:nil];
}
- (void)endSessionAndCancelTasks
{
if (_session)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Ended and Tasks Cancelled");
#endif
[self.session invalidateAndCancel];
[self cleanupSession];
}
}
#pragma mark -
#pragma mark - NSURLSession Delegate Methods
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Completed");
#endif
if (error)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Photo Request Fetch: %@", [error description]);
#endif
[self endSessionAndCancelTasks];
switch (error.code)
{
case NSURLErrorTimedOut:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"RequestTimedOut" object:self];
}
break;
case NSURLErrorCancelled:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"RequestCancelled" object:self];
}
break;
case NSURLErrorNotConnectedToInternet:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"NotConnectedToInternet" object:self];
}
break;
case NSURLErrorNetworkConnectionLost:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkConnectionLost" object:self];
}
break;
default:
{
}
break;
}
}
else {
if ([task isEqual:_photoDataTask])
{
[self parseData:self.photoData fromTask:task];
}
}
}
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Invalidation: %@", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self endSessionAndCancelTasks];
}
}
#pragma mark -
#pragma mark - NSURLSessionDataTask Delegate Methods
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Received Data");
#endif
if ([dataTask isEqual:_photoDataTask])
{
[self.photoData appendData:data];
}
}
#pragma mark -
问题:
为什么NSURLSession
导致这些内存泄漏?当我完成它时,我使NSURLSession
无效。我还发布了我不需要的任何属性,并将会话设置为nil(参考 - (void)cleanupSession& - (void)endSessionAndCancelTasks)。
其他信息:
会话完成并“清理”后会发生内存泄漏。有时,它们也会在我弹出UIViewController
之后出现。但是,我的所有自定义(GPPhotoRequest和GPSearch)对象和UIViewController都被解除分配(我通过添加NSLog确保了这一点。)
我尽量不发布很多代码,但我觉得大部分都需要被看到。
如果您需要更多信息,请与我们联系。非常感谢帮助。
答案 0 :(得分:43)
当我切换到NSURLSession时,我遇到了同样的“漏洞”,内存管理问题。对我来说,在创建会话并恢复/启动dataTask之后,我从未告诉会话自我清理(即释放分配给它的内存)。
// Ending my request method with only the following line causes memory leaks
[dataTask resume];
// Adding this line fixed my memory management issues
[session finishTasksAndInvalidate];
来自docs:
在最后一个任务完成并且会话进行最后一次委托调用之后,对委托和回调对象的引用就会被破坏。
清理我的会话修复了通过Instruments报告的内存泄漏。
答案 1 :(得分:4)
重新阅读URL Loading System Programming Guide后,我将NSURLSession
属性设置为nil得太早。
相反,我需要在收到委托消息NSURLSession
之后将URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
属性设置为nil,这是有道理的。幸运的是,这是一个小错误。
E.g。
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Invalidation: %@", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self cleanupSession];
}
}
答案 2 :(得分:1)
有同样的问题。 @ Jonathan的回答没有任何意义 - 我的应用程序仍然泄露了记忆。我发现在URLSession:didBecomeInvalidWithError:
委托方法中将session属性设置为nil会导致问题。试图深入研究URL Loading System Programming Guide。它说
在使会话无效后,当所有未完成的任务都已取消或已完成时,会话将向委托发送URLSession:didBecomeInvalidWithError:消息。当该委托方法返回时,会话将其强引用释放给委托。
我将委托方法留空了。但是无效的session
属性仍然有一个指针,我什么时候应该设置它nil
?我只是将此属性设置为弱
// .h-file
@interface MyClass : NSObject <NSURLSessionDelegate>
{
__weak NSURLSession *_session;
}
// .m-file
- (NSURLSessionTask *)taskForRequest:(NSURLRequest *)request withCompletionHandler:(void(^)(NSData *,NSURLResponse *,NSError *))handler
{
if(!_session)
[self initSession];
//...
}
该应用程序停止了内存泄漏。
答案 3 :(得分:1)
请在这里查看我的答案:https://stackoverflow.com/a/53428913/4437636
我相信此泄漏与我所见相同,并且仅在通过代理运行网络流量时发生。我的代码很好,但事实证明,Apple API的内部错误导致泄漏。