在IOS中使用web服务时发生NSURLSession内存泄漏

Posted

技术标签:

【中文标题】在IOS中使用web服务时发生NSURLSession内存泄漏【英文标题】:NSURLSession Memory Leaks occur when using web services in IOS 【发布时间】:2014-02-28 13:40:42 【问题描述】:

我正在构建一个使用 Web 服务的应用程序,并从我使用 NSURLSessionNSURLSessionDataTask 的 Web 服务获取信息。

我现在处于内存测试阶段,我发现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 无效。我还将释放我不需要的任何属性并将会话设置为零(请参阅 - (void)cleanupSession & - (void) endSessionAndCancelTask​​s)。

其他信息: 会话完成并“清理”后会发生内存泄漏。有时,它们也会在我弹出UIViewController 之后发生。但是,我所有的自定义(GPPhotoRequest 和 GPSearch)对象和 UIViewController 都被释放了(我已经通过添加 NSLog 来确保)。

我尽量不发布太多代码,但我觉得大部分代码都需要看到。

如果您需要更多信息,请告诉我。非常感谢您的帮助。

【问题讨论】:

【参考方案1】:

请在此处查看我的回答:https://***.com/a/53428913/4437636

我相信这个泄漏与我看到的相同,并且仅在通过代理运行网络流量时发生。我的代码很好,但事实证明是 Apple API 中的一个内部错误导致了泄漏。

【讨论】:

我知道这可能不是问题的答案,但这些奇怪的泄漏折磨了我几个月。问题是我在调试应用程序时总是使用代理。然后我看到这个答案并尝试不使用代理。砰,泄漏消失了。 @andreas777 是的,我遇到了完全相同的问题(见上文)。我已经向苹果公司提交了雷达,但他们还没有回复。我几个月前提交的。【参考方案2】:

当我切换到 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 报告的内存泄漏。

【讨论】:

也来自文档:“重要提示:在 sharedSession 方法返回的会话上调用此方法无效。” :( 你成就了我的一天 :) 我有 18 次泄漏,当添加此无效方法时 - 0 次泄漏【参考方案3】:

有同样的问题。 @Jonathan 的回答没有任何意义——我的应用程序仍然泄漏了内存。我发现在URLSession:didBecomeInvalidWithError: 委托方法中将会话属性设置为 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];
  //...

应用程序停止泄漏内存。

【讨论】:

我试过了,没用。上面的 John Erck 回答有效。【参考方案4】:

重读URL Loading System Programming Guide 后发现我将NSURLSession 属性设置为nil 为时过早。

相反,我需要在收到委托消息URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error 之后将NSURLSession 属性设置为nil,这是有道理的。幸运的是,这是一个小错误。

例如

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error

    if (error)
    

#ifdef DEBUG
        NSLog(@"[GPPhotoRequest] Session Invalidation: %@", [error description]);
#endif

    

    if ([session isEqual:_session])
    
        [self cleanupSession];
    

【讨论】:

重提一个老问题,但我也有同样的问题,你的解决方法对我没有解决? @Sammio2 看看我下面的答案。它可以帮助你解决我的问题。 请注意,在最初向您的应用收费后,安全框架分配仍将保留 10 分钟。因为无论您做什么,用户都无法手动清除它们。系统会处理它,因为安全性。从安全库中观察这些增长,并在它们首次出现 10 分钟后看到它们消失。在这里查看我的答案***.com/questions/28223345/…

以上是关于在IOS中使用web服务时发生NSURLSession内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

NSURLSession详解

iOS网络2——NSURLSession

为啥当我在 ios 应用程序中使用 alamofire 时来自服务器的字符串响应会发生变化?

使用 POST 将用户信息存储在 Web 服务器 IOS 应用程序中

iOS开发之网络编程--5NSURLSessionUploadTask+NSURLSessionDataDelegate代理上传

iOS开发之网络编程--1NSURLSession的基本使用