首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >递归/迭代NSURLSessionDataTask导致内存泄漏

递归/迭代NSURLSessionDataTask导致内存泄漏
EN

Stack Overflow用户
提问于 2014-09-08 10:08:02
回答 4查看 2.8K关注 0票数 8

我有一个问题,内存泄漏在我的代码,我有一个需要得到许多URL的快速连续,每个GET受到影响的结果上一个GET。其目的是在响应中查找特定的内容。

我发现最干净的方法是递归实现这一点,因为我可以使用相同的方法来识别响应中是否存在所需的值。在功能上,它工作得很好,但是它泄漏内存,如下所述。我还以迭代的方式实现了相同的功能,这也会泄漏内存。

在我看来,似乎是NSURLSession API负责泄漏这个内存,而且只有在多个调用非常快地连续进行时才会发生。不过,如果有人能指出我正在犯的任何明显错误,我将不胜感激。

更新10/09/14:

更新后添加一个递归计数器,演示即使代码没有执行无限次也仍然会发生泄漏。还稍微整理了实现,在视图控制器中重新使用NSURLSessionNSURLSessionConfiguration作为属性。

样本代码:

代码语言:javascript
复制
- (void)performURLCallRecursive {

    recursionLimiter++;

    if (recursionLimiter > 10) {
        [self.session finishTasksAndInvalidate];
        return;
    }

    NSURL * checkURL = [NSURL URLWithString:@"http://www.google.com"];

    __block NSMutableURLRequest * urlRequest = [[NSMutableURLRequest alloc] initWithURL:checkURL
                                                                            cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                                        timeoutInterval:0.0f];

    __weak typeof(self) weakSelf = self;

    NSURLSessionDataTask * task = [self.session dataTaskWithRequest:urlRequest
                                                  completionHandler:^(NSData *data, NSURLResponse *response, NSError
*error) {

                                                      NSString * body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                                                      NSLog(@"Body: %@", body);

                                                      [weakSelf performURLCallRecursive];

                                                  }];

    [task resume];
 }

#pragma mark - Getters

- (NSURLSessionConfiguration *)sessionConfiguration {

    if (!_sessionConfiguration) {

        _sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];

        [_sessionConfiguration setAllowsCellularAccess:NO];
        [_sessionConfiguration setTimeoutIntervalForRequest:10.0f];
        [_sessionConfiguration setTimeoutIntervalForResource:10.0f];

        [_sessionConfiguration setURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]];

    }

    return _sessionConfiguration;
 }

- (NSURLSession *)session {

    if (_session == nil) {

        _session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration
                                                 delegate:[SPRSessionDelegate new]
                                            delegateQueue:nil];

    }

    return _session; 
}

如仪器报告的那样,内存泄漏。(注意:每次这些情况略有不同,但大多数情况下都含有相同的泄漏,只是或多或少地含有相同的漏洞):

进一步更新:

因此,我实际上迭代地实现了相同的代码,内存泄漏仍然发生。在这个例子中,我包含了一个循环限制器,这样它就不会永远执行。有人能帮我弄清楚到底是怎么回事吗?

代码语言:javascript
复制
- (void)performURLCallIterative
{

    int loopLimiter = 0;

    do {

        NSURLSessionConfiguration * defaultSession = [NSURLSessionConfiguration defaultSessionConfiguration];

        [defaultSession setAllowsCellularAccess:NO];
        [defaultSession setTimeoutIntervalForRequest:10.0f];
        [defaultSession setTimeoutIntervalForResource:10.0f];

        NSURLSession * session = [NSURLSession sessionWithConfiguration:defaultSession
                                                               delegate:self
                                                          delegateQueue:nil];


        NSURL * checkURL = [NSURL URLWithString:@"http://google.com"];

        NSMutableURLRequest * urlRequest = [[NSMutableURLRequest alloc] initWithURL:checkURL
                                                                        cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                                    timeoutInterval:0.0f];

        __weak NSURLSession * weakSession = session;

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

        NSURLSessionDataTask * task = [session dataTaskWithRequest:urlRequest
                                                 completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

                                                     NSString * body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                                                     NSLog(@"Body: %@", body);

                                                     dispatch_semaphore_signal(semaphore);

                                                     [weakSession invalidateAndCancel];

                                                 }];

        [task resume];

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        loopLimiter++;

    } while (loopLimiter <= 6);

}

更新10/09/14:

对于任何可能已经找到自己的途径的谷歌人来说,iOS 8上仍然存在这种情况。就我而言,这是iOS中的一个bug。

EN

回答 4

Stack Overflow用户

发布于 2014-09-09 08:16:01

-更新9/12/2014

解决方案:等待iOS8。

-更新9/10/2014

哇,这是螺旋进入了复杂性的第N维:P。我希望你能很快地在这里得到一个突破。

我还有几件事要你试试。

( 1)你能确保NSZombies被关掉吗?在Xcode中,Product->Scheme->Edit Scheme.>启用Zombie对象(没有勾选)。

2)还可以为您的cachePolicy:NSURLCacheStorageNotAllowed尝试NSMutableURLRequest

( 3)你能看到你是否有错误完成吗?把这个放在你的身体线作业上..。

代码语言:javascript
复制
if (error == nil)
{
    //Enter data->string code here
}

( 4)你能看一下你是否没有获得200的地位吗?

代码语言:javascript
复制
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];

5)很难准确地想象你的项目是如何设置的。我将有一个包含NSURLSession方法的NSURLSession类型类,它与调用它的UIViewController类是分开的。然后,计时器或您希望选择的任何递归方法将从UIViewController调用url会话关联的方法。

-更新9/9/2014

你对我的问题是正确的。数据任务在完成之前被恢复,在数据任务完成后,会话无效。我还没见过这样的情况,但这是有道理的。刚刚在我的测试,没有泄漏的会话invalidateAndCancel.

你能检查你的完成处理程序是否执行吗?也许没有,在新任务启动之前,会话从未被取消过?

我注意到在“仪器泄漏报告”中有一些对headers的引用,也许如果您没有指定urlRequest setHTTPMethod:@"GET“请求缺少一些基本的标头?

(我会在找到解决方案后编辑,这样看起来就不像讨论了)。

- 2014年8月9日原文

有趣的问题!我有一些与NSURLSessions相关的棘手的漏洞。当然,@autoreleasepool{}和其他人是目前为止尝试的好建议.但!

恐怕你让我们过去的东西可能是这里的罪魁祸首。

首先,我要做几点观察:

1)我不清楚你为什么要在这里__weak自我。你试图避免的保留周期是什么?也许在您实际使用的代码中,除了“示例”之外,这一点更清楚。

2)在与会话相关的数据任务甚至有机会完成之前,调用使会话无效的原因是什么,更不用说恢复了。数据任务处于挂起状态,直到恢复。

3)如果您正在递归地运行这样的方法,那么我认为指定或至少考虑哪个委托队列是至关重要的,否则将其设置为0默认为串行操作队列。当委托在完成处理程序完成之前调用时,在一个无限循环中会发生什么?

--

我认为这里的主要问题是,在NSURLSessionDataTask有机会完成之前,您正在启动一个新的或取消它。看看+sesssionWithConfiguration:

(抱歉还不能包括图片,希望在这个答案之后)

ref/occ/clm/NSURLSession/sessionWithConfiguration

重点在这里..。

重要 会话对象保持对委托的强烈引用,直到应用程序显式地使会话无效。如果不通过调用invalidateAndCancel或resetWithCompletionHandler:方法使会话无效,则应用程序会泄漏内存。

我的建议是..。

代码语言:javascript
复制
 //Your code above...
    [task resume];
    [session finishTasksAndInvalidate];
}

理论上,这应该阻止任何新会话在完成之前启动,根据描述,“不能在会话中创建...new任务,但现有任务将一直持续到完成。在最后一个任务完成并且会话进行最后一次委托调用之后,对委托和回调对象的引用就会中断.”

在复会之前,我仍然不确定是否使会议无效。

我希望这能帮到你。祝好运。

票数 4
EN

Stack Overflow用户

发布于 2014-09-11 12:11:54

一位开发人员对苹果的支持请求显示,这是iOS 7中的一个bug。上面发布的代码示例(递归或迭代)没有错误,据报道,它已经在iOS 8 GM版本中得到了修复。

更新:

这种情况在iOS 8.1中仍然存在。

票数 3
EN

Stack Overflow用户

发布于 2016-03-05 20:43:06

来自NSURLSession的内存有很多问题,我最终通过不为每个请求使用一个新会话来修复它。在Wikipedia上,会话通常被定义为:

一种半永久性的交互式信息交换

因此,苹果的方便类方法[NSURLSession sharedSession]为我们提供了一个关于NSURLSession对象打算如何使用的线索:作为半永久性对象,而不是像您所做的那样为每个请求创建新的一次性对象。

您正在为大量请求创建一个新的会话对象,从服务器的角度来看,这些请求都是单个客户端的单个会话的一部分。

我也在做同样的事情,直到我意识到这是我痛苦的根源。我没有很清楚地发现苹果的文档,但当我意识到我的方式错误之后,文档中的某些东西突然变得更有意义了,比如为什么有一个sharedSession单例的NSURLSession方便方法,为什么“任务”在finishTasksAndInvalidate中是复数的,为什么他们称它为“会话”,为什么它有一个缓存,等等(如果只是为了一个请求,为什么它会是一个“会话”,一个“缓存”会有什么好处呢?)

了解像Safari这样的浏览器如何看待会话是很有帮助的。新会话在第一次连接到给定服务器时开始。设置会话包括创建SSL证书缓存、建立身份验证、握手等。每次页面上的某些JavaScript向同一台服务器发出新请求时,所有这些都会非常低效,特别是因为现代web应用程序经常通过回调等方式发出请求。这就是为什么为大量的请求和响应(如果需要,客户机和服务器之间的话)建立一个会话的原因。最终,一个会话将过期,但通常在几分钟后才会发生,而不是在一个请求之后!

关键是,您应该如何使用NSURLSession对象,这就是将具有强引用的NSURLSession对象作为属性的单例。如果您需要自定义会话的配置(例如关闭缓存等),请执行此操作。然而,如果您不需要自定义它,只需使用苹果的sharedSession

如果在自定义类上使用单例,那么,如果不需要将会话属性设置为零,则不需要使用invalidateAndCancelfinishTasksAndInvalidate。相反,只需定期清除连接缓存的resetWithCompletionBlockflushWithCompletionBlock即可。

如果您讨厌单个用户,您仍然可以使用会话作为属性,只需确保在会话的最后所有者被ARC运行时解除之前,invalidateAndCancelfinishTasksAndInvalidate即可。

还请注意,将NSURLSession对象的URLCache属性设置为nil是关闭缓存的正确方法。这就是苹果说他们为backgroundSessionConfiguration所做的事情。

请参阅我对这个主题herehere的其他答案。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25721825

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档