无法修复 Objective-C ARC 多线程 iOS 应用程序中的特定内存泄漏
Can't fix a specific memory leak in Objective-C ARC multithreading iOS app
我几乎要竭尽全力修复仪器报告的内存泄漏问题:
- (BOOL)moveCloudFileToLocal: (NSString*)cloudFilePath error: (NSError**)error
{
// each variable starting with an underscore is an ivar
BOOL bSuccess = NO;
@autoreleasepool
{
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* docPathLocal = [paths objectAtIndex:0];
NSURL* sourceURL = [NSURL fileURLWithPath:cloudFilePath]; // => LEAKING!!!
NSString* destFileName = sourceURL.lastPathComponent;
NSURL* destFileURL = [[NSURL fileURLWithPath:docPathLocal]
URLByAppendingPathComponent:destFileName]; // => LEAKING!!!
NSArray* arrArgs = @[ sourceURL, destFileURL ]; // => LEAKING!!!
NSThread* thread = [[NSThread alloc] initWithTarget: self
selector: @selector(threadMoveCloudFileToLocal:)
object: arrArgs];
_threadCloud = thread;
[_threadCloud start];
}
[self waitForMovingThreadToFinish];
if (error != nil)
*error = _retError;
bSuccess = (_retError == nil);
return bSuccess;
}
Instruments 告诉我 arrArgs、sourceURL 和 destFileURL 会造成这样的根泄漏:
更多信息是 Instruments 的输出,但我不熟悉结果...所以我不知道我必须采取哪种措施来修复泄漏:
我还尝试以不同的方式编写以下三个变量:
NSURL __autoreleasing* sourceURL...;
NSURL __autoreleasing* destFileURL...;
NSArray __autoreleasing* arrArgs...;
不幸的是,这并没有改变任何东西,它一直以同样的方式泄漏。好的,它没有崩溃,但它泄漏了,我想修复它。我在 OS X 10.9.5 上使用 Xcode 6.1.1,使用 iPhone 模拟器和 iOS 7.0.3
这里有什么提示吗?
按要求编辑,附加代码部分:
- (void)threadMoveCloudFileToLocal: (NSArray*)args
{
// args:
// 1: sourceURL = iCloud file
// 2: destFileURL = local file
//
_bgTaskExpired = NO;
@autoreleasepool
{
UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{ self->_bgTaskExpired = YES; }];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
if ([args count] != 2)
goto finished;
{
NSError* lastError = nil;
NSURL* sourceURL = [args objectAtIndex:0]; // sourceURL is the iCloud file
__block NSURL* destFileURL = [args objectAtIndex:1]; // destFileURL is the local file
__block NSError* theError = nil;
__block BOOL bSuccess = NO;
_retError = nil;
_fileCoord = [[NSFileCoordinator alloc] initWithFilePresenter:self];
[_fileCoord coordinateWritingItemAtURL: sourceURL
options: NSFileCoordinatorWritingForDeleting
error: &lastError
byAccessor: ^(NSURL* newURL)
{
NSFileManager* fileManager = [[NSFileManager alloc] init];
if (AfxGetApp().numVersion >= 6.0)
{
bSuccess = [fileManager setUbiquitous:NO itemAtURL:newURL destinationURL:destFileURL error:&theError];
if (bSuccess)
{
theError = nil;
bSuccess = [fileManager evictUbiquitousItemAtURL:newURL error:&theError];
if (!bSuccess && [iCloudSupport errorIsFileNotFound:theError])
{
bSuccess = YES;
theError = nil;
}
}
}
else
{
// on iPad1 with iOS 5.1.1 it is hanging with the above procedure
//...code not being shared, not relevant
}
if (bSuccess)
[self removeFilePathFromContainers:sourceURL.path];
}];
_fileCoord = nil;
if (lastError == nil && theError != nil)
lastError = theError;
if (!bSuccess && lastError == nil)
lastError = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:nil];
_retError = lastError;
}
finished:
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
} // end @autoreleasepool
[NSThread exit];
}
- (void)waitForMovingThreadToFinish
{
while (_threadCloud && ![_threadCloud isFinished])
{
// Wait for the thread to finish.
[NSThread sleepForTimeInterval:0.1];
}
}
顺便说一句:静态分析器在我的代码中没有找到任何东西,一个警告也没有。它仅在 运行 动态显示。该代码来自我们刚推出 iOS 6 和 iOS 7 时的代码。当时使用旧的 SDK,我没有任何抱怨。即使 evictUbiquitousItemAtURL: 在与协调写作一起使用时也没有在文档中标记为死锁 - 正如今天在当前文档中那样。尽管 运行ning 在 iOS 5.1.1 上它从未死锁 - 这就是为什么我有单独的条件 else 部分。
编辑:额外的乐器输出更清晰
在我更改几行代码之前,我从 Instruments 获取了这个代码视图:
更改几行代码以构建在线程创建语句中作为对象传递的参数数组后,它在 Instruments 中看起来非常有趣,因为它恰好只是 NSArray 实例化行泄漏。不幸的是,我既不明白为什么也不知道如何解决这个问题:-(
您需要删除对 [NSThread exit]
的调用。它会阻止您线程上的根自动释放池耗尽。
+ (void) exit
的文档说:
Invoking this method should be avoided as it does not give your thread
a chance to clean up any resources it allocated during its execution.
我几乎要竭尽全力修复仪器报告的内存泄漏问题:
- (BOOL)moveCloudFileToLocal: (NSString*)cloudFilePath error: (NSError**)error
{
// each variable starting with an underscore is an ivar
BOOL bSuccess = NO;
@autoreleasepool
{
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* docPathLocal = [paths objectAtIndex:0];
NSURL* sourceURL = [NSURL fileURLWithPath:cloudFilePath]; // => LEAKING!!!
NSString* destFileName = sourceURL.lastPathComponent;
NSURL* destFileURL = [[NSURL fileURLWithPath:docPathLocal]
URLByAppendingPathComponent:destFileName]; // => LEAKING!!!
NSArray* arrArgs = @[ sourceURL, destFileURL ]; // => LEAKING!!!
NSThread* thread = [[NSThread alloc] initWithTarget: self
selector: @selector(threadMoveCloudFileToLocal:)
object: arrArgs];
_threadCloud = thread;
[_threadCloud start];
}
[self waitForMovingThreadToFinish];
if (error != nil)
*error = _retError;
bSuccess = (_retError == nil);
return bSuccess;
}
Instruments 告诉我 arrArgs、sourceURL 和 destFileURL 会造成这样的根泄漏:
更多信息是 Instruments 的输出,但我不熟悉结果...所以我不知道我必须采取哪种措施来修复泄漏:
我还尝试以不同的方式编写以下三个变量:
NSURL __autoreleasing* sourceURL...;
NSURL __autoreleasing* destFileURL...;
NSArray __autoreleasing* arrArgs...;
不幸的是,这并没有改变任何东西,它一直以同样的方式泄漏。好的,它没有崩溃,但它泄漏了,我想修复它。我在 OS X 10.9.5 上使用 Xcode 6.1.1,使用 iPhone 模拟器和 iOS 7.0.3
这里有什么提示吗?
按要求编辑,附加代码部分:
- (void)threadMoveCloudFileToLocal: (NSArray*)args
{
// args:
// 1: sourceURL = iCloud file
// 2: destFileURL = local file
//
_bgTaskExpired = NO;
@autoreleasepool
{
UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{ self->_bgTaskExpired = YES; }];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
if ([args count] != 2)
goto finished;
{
NSError* lastError = nil;
NSURL* sourceURL = [args objectAtIndex:0]; // sourceURL is the iCloud file
__block NSURL* destFileURL = [args objectAtIndex:1]; // destFileURL is the local file
__block NSError* theError = nil;
__block BOOL bSuccess = NO;
_retError = nil;
_fileCoord = [[NSFileCoordinator alloc] initWithFilePresenter:self];
[_fileCoord coordinateWritingItemAtURL: sourceURL
options: NSFileCoordinatorWritingForDeleting
error: &lastError
byAccessor: ^(NSURL* newURL)
{
NSFileManager* fileManager = [[NSFileManager alloc] init];
if (AfxGetApp().numVersion >= 6.0)
{
bSuccess = [fileManager setUbiquitous:NO itemAtURL:newURL destinationURL:destFileURL error:&theError];
if (bSuccess)
{
theError = nil;
bSuccess = [fileManager evictUbiquitousItemAtURL:newURL error:&theError];
if (!bSuccess && [iCloudSupport errorIsFileNotFound:theError])
{
bSuccess = YES;
theError = nil;
}
}
}
else
{
// on iPad1 with iOS 5.1.1 it is hanging with the above procedure
//...code not being shared, not relevant
}
if (bSuccess)
[self removeFilePathFromContainers:sourceURL.path];
}];
_fileCoord = nil;
if (lastError == nil && theError != nil)
lastError = theError;
if (!bSuccess && lastError == nil)
lastError = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:nil];
_retError = lastError;
}
finished:
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
} // end @autoreleasepool
[NSThread exit];
}
- (void)waitForMovingThreadToFinish
{
while (_threadCloud && ![_threadCloud isFinished])
{
// Wait for the thread to finish.
[NSThread sleepForTimeInterval:0.1];
}
}
顺便说一句:静态分析器在我的代码中没有找到任何东西,一个警告也没有。它仅在 运行 动态显示。该代码来自我们刚推出 iOS 6 和 iOS 7 时的代码。当时使用旧的 SDK,我没有任何抱怨。即使 evictUbiquitousItemAtURL: 在与协调写作一起使用时也没有在文档中标记为死锁 - 正如今天在当前文档中那样。尽管 运行ning 在 iOS 5.1.1 上它从未死锁 - 这就是为什么我有单独的条件 else 部分。
编辑:额外的乐器输出更清晰
在我更改几行代码之前,我从 Instruments 获取了这个代码视图:
更改几行代码以构建在线程创建语句中作为对象传递的参数数组后,它在 Instruments 中看起来非常有趣,因为它恰好只是 NSArray 实例化行泄漏。不幸的是,我既不明白为什么也不知道如何解决这个问题:-(
您需要删除对 [NSThread exit]
的调用。它会阻止您线程上的根自动释放池耗尽。
+ (void) exit
的文档说:
Invoking this method should be avoided as it does not give your thread a chance to clean up any resources it allocated during its execution.