我有一个问题,内存泄漏在我的代码,我有一个需要得到许多URL的快速连续,每个GET受到影响的结果上一个GET。其目的是在响应中查找特定的内容。
我发现最干净的方法是递归实现这一点,因为我可以使用相同的方法来识别响应中是否存在所需的值。在功能上,它工作得很好,但是它泄漏内存,如下所述。我还以迭代的方式实现了相同的功能,这也会泄漏内存。
在我看来,似乎是NSURLSession API负责泄漏这个内存,而且只有在多个调用非常快地连续进行时才会发生。不过,如果有人能指出我正在犯的任何明显错误,我将不胜感激。
更新10/09/14:
更新后添加一个递归计数器,演示即使代码没有执行无限次也仍然会发生泄漏。还稍微整理了实现,在视图控制器中重新使用NSURLSession和NSURLSessionConfiguration作为属性。
样本代码:
- (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;
}如仪器报告的那样,内存泄漏。(注意:每次这些情况略有不同,但大多数情况下都含有相同的泄漏,只是或多或少地含有相同的漏洞):

进一步更新:
因此,我实际上迭代地实现了相同的代码,内存泄漏仍然发生。在这个例子中,我包含了一个循环限制器,这样它就不会永远执行。有人能帮我弄清楚到底是怎么回事吗?
- (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。
发布于 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)你能看到你是否有错误完成吗?把这个放在你的身体线作业上..。
if (error == nil)
{
//Enter data->string code here
}( 4)你能看一下你是否没有获得200的地位吗?
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:方法使会话无效,则应用程序会泄漏内存。
我的建议是..。
//Your code above...
[task resume];
[session finishTasksAndInvalidate];
}理论上,这应该阻止任何新会话在完成之前启动,根据描述,“不能在会话中创建...new任务,但现有任务将一直持续到完成。在最后一个任务完成并且会话进行最后一次委托调用之后,对委托和回调对象的引用就会中断.”
在复会之前,我仍然不确定是否使会议无效。
我希望这能帮到你。祝好运。
发布于 2014-09-11 12:11:54
一位开发人员对苹果的支持请求显示,这是iOS 7中的一个bug。上面发布的代码示例(递归或迭代)没有错误,据报道,它已经在iOS 8 GM版本中得到了修复。
更新:
这种情况在iOS 8.1中仍然存在。
发布于 2016-03-05 20:43:06
来自NSURLSession的内存有很多问题,我最终通过不为每个请求使用一个新会话来修复它。在Wikipedia上,会话通常被定义为:
一种半永久性的交互式信息交换
因此,苹果的方便类方法[NSURLSession sharedSession]为我们提供了一个关于NSURLSession对象打算如何使用的线索:作为半永久性对象,而不是像您所做的那样为每个请求创建新的一次性对象。
您正在为大量请求创建一个新的会话对象,从服务器的角度来看,这些请求都是单个客户端的单个会话的一部分。
我也在做同样的事情,直到我意识到这是我痛苦的根源。我没有很清楚地发现苹果的文档,但当我意识到我的方式错误之后,文档中的某些东西突然变得更有意义了,比如为什么有一个sharedSession单例的NSURLSession方便方法,为什么“任务”在finishTasksAndInvalidate中是复数的,为什么他们称它为“会话”,为什么它有一个缓存,等等(如果只是为了一个请求,为什么它会是一个“会话”,一个“缓存”会有什么好处呢?)
了解像Safari这样的浏览器如何看待会话是很有帮助的。新会话在第一次连接到给定服务器时开始。设置会话包括创建SSL证书缓存、建立身份验证、握手等。每次页面上的某些JavaScript向同一台服务器发出新请求时,所有这些都会非常低效,特别是因为现代web应用程序经常通过回调等方式发出请求。这就是为什么为大量的请求和响应(如果需要,客户机和服务器之间的话)建立一个会话的原因。最终,一个会话将过期,但通常在几分钟后才会发生,而不是在一个请求之后!
关键是,您应该如何使用NSURLSession对象,这就是将具有强引用的NSURLSession对象作为属性的单例。如果您需要自定义会话的配置(例如关闭缓存等),请执行此操作。然而,如果您不需要自定义它,只需使用苹果的sharedSession。
如果在自定义类上使用单例,那么,如果不需要将会话属性设置为零,则不需要使用invalidateAndCancel或finishTasksAndInvalidate。相反,只需定期清除连接缓存的resetWithCompletionBlock或flushWithCompletionBlock即可。
如果您讨厌单个用户,您仍然可以使用会话作为属性,只需确保在会话的最后所有者被ARC运行时解除之前,invalidateAndCancel或finishTasksAndInvalidate即可。
还请注意,将NSURLSession对象的URLCache属性设置为nil是关闭缓存的正确方法。这就是苹果说他们为backgroundSessionConfiguration所做的事情。
https://stackoverflow.com/questions/25721825
复制相似问题