无法修复 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.