Ios 在循环中使用dataWithContentsOfURL的内存泄漏

Ios 在循环中使用dataWithContentsOfURL的内存泄漏,ios,objective-c,cocoa-touch,memory-leaks,block,Ios,Objective C,Cocoa Touch,Memory Leaks,Block,我使用的是Xcode 4.6.3和iOS 5.5/6.1.6 我使用后台线程将大量jpg从服务器加载到iOS设备 dispatch_async(kBgQueue, ^ { // get the array of filenames to download NSURL* url = [NSURL URLWithString:webPath]; NSArra

我使用的是Xcode 4.6.3和iOS 5.5/6.1.6

我使用后台线程将大量jpg从服务器加载到iOS设备

dispatch_async(kBgQueue, ^
               {
                   // get the array of filenames to download
                   NSURL* url = [NSURL URLWithString:webPath];
                   NSArray* theArray = [NSArray arrayWithContentsOfURL:url];
                   if( theArray )
                   {
                       dispatch_async(dispatch_get_main_queue(), ^{
                           // disable screen buttons
                           [self setButtons:false];
                       });

                       [self loadImagesFromList:theArray sourceBundle:bundlePath destBundle:localBundlePath manager:manager];

                       if (!stopFlag) {
                           // if no memory error has occurred
                           NSLog(@"calling refresh after load_images");
                           dispatch_async(dispatch_get_main_queue(), ^{
                               [self refresh];
                           });
                       }
                       theArray = nil;
                   }
                   else
                   {
                       NSLog(@"Error loading bundle");
                   }
               });
背景法:

-(void)loadImagesFromList:(NSArray *)theArray
             sourceBundle:(NSString *)bundlePath
               destBundle:(NSString *)localBundlePath
                  manager:(NSFileManager *)manager {

    // initialize the progress and activity indicator
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.activityIndictor startAnimating];
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
        [self.progressIndictor setProgress:0 animated:NO];
    });

    NSURL *url;
    NSString *srcFile;
    NSString *destFile;
    NSError *error = nil;

    int counter = 0;
    float prog = 0;
    float increment = 1.0 / [theArray count];
    float stepSize = [theArray count] / 10;

    for (NSString *file in theArray)
    {
        if (stopFlag) {
            NSLog(@"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
            return;
        }
        srcFile = [bundlePath stringByAppendingPathComponent:file];
        destFile = [localBundlePath stringByAppendingPathComponent:file];

        counter += 1;
        prog += increment;
        if (counter == stepSize) {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.progressIndictor.progress = prog;
            });
            counter = 0;
        }

        // only download if file isn't already here
        BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
        if (!fileExists) {
            // jpg or folder check
            if ([[destFile pathExtension] isEqualToString:@"jpg"]) {
                url = [NSURL URLWithString:srcFile];
                data = [NSData dataWithContentsOfURL:url
                                             options:0
                                               error:&error];

                [data writeToFile:destFile options:NSDataWritingAtomic error:&error];
                data = nil;
            } else {
                [manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
            }
        }
    }
}
如果文件存在,循环将通过数组压缩并退出到主线程ok。 如果缺少任何文件,下载/写入部分似乎会占用RAM并触发内存不足警告。这需要数千个文件才能完成

我已经尝试在循环外声明变量,甚至在主线程中执行整个操作,以测试这是否是导致泄漏的原因。 我尝试使用替换数据with contentsofURL:options:error调用。 我试过仪器,但速度很慢而且经常崩溃。在崩溃之前,它确实显示分配在上升,上升,缓慢上升


经过几天的讨论,我被难住了。

我建议的第一件事是使用
@autoreleasepool
来控制消耗的最大内存量。现在,您正在将内容作为自动释放对象下载到
NSData
中,完成后,您将
nil
-ing该变量,该变量将简单地标记为在自动释放池耗尽后将其释放(在
loadImagesFromList
完成之前不会发生)。通过(a)在
for
循环中移动变量声明;(b)将其包装在一个
@autoreleasepool
中,随着个人下载的完成,您的内存将被释放

-(void)loadImagesFromList:(NSArray *)theArray
             sourceBundle:(NSString *)bundlePath
               destBundle:(NSString *)localBundlePath
                  manager:(NSFileManager *)manager {

    // initialize the progress and activity indicator
    dispatch_async(dispatch_get_main_queue(), ^{
        // your UI update here
    });

    int counter = 0;
    float prog = 0;
    float increment = 1.0 / [theArray count];
    float stepSize = [theArray count] / 10;

    for (NSString *file in theArray)
    {
        @autoreleasepool {
            if (stopFlag) {
                NSLog(@"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
                return;
            }

            NSString *srcFile = [bundlePath stringByAppendingPathComponent:file];
            NSString *destFile = [localBundlePath stringByAppendingPathComponent:file];

            counter += 1;
            prog += increment;
            if (counter == stepSize) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    self.progressIndictor.progress = prog;
                });
                counter = 0;
            }

            // only download if file isn't already here
            BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
            if (!fileExists) {
                NSError *error = nil;

                // jpg or folder check
                if ([[destFile pathExtension] isEqualToString:@"jpg"]) {
                    NSURL *url = [NSURL URLWithString:srcFile];
                    NSData *data = [NSData dataWithContentsOfURL:url
                                                         options:0
                                                           error:&error];

                    [data writeToFile:destFile options:NSDataWritingAtomic error:&error];
                } else {
                    [manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
                }
            }
        }
    }
}

您可能需要重构此代码以使用
NSOperationQueue
。这解决了峰值内存问题,但也让您享受一定程度的并发性。由于iOS只允许4-5个并发请求,因此您希望将并发操作的最大数量限制在合理的数量,这样可以在尝试运行太多并发请求时降低网络超时风险。(此
maxConcurrentOperationCount
功能是我建议使用操作队列的主要原因。)

无论如何,这可能看起来像:

-(void)loadImagesFromList:(NSArray *)theArray
             sourceBundle:(NSString *)bundlePath
               destBundle:(NSString *)localBundlePath
                  manager:(NSFileManager *)manager {

    // initialize the progress and activity indicator
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // your UI update here
    }];

    int   __block counter = 0;
    float __block prog = 0;
    float increment = 1.0 / [theArray count];
    float stepSize = [theArray count] / 10;

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 4;

    for (NSString *file in theArray)
    {
        [queue addOperationWithBlock:^{
            if (stopFlag) {
                NSLog(@"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
                return;
            }

            NSString *srcFile = [bundlePath stringByAppendingPathComponent:file];
            NSString *destFile = [localBundlePath stringByAppendingPathComponent:file];

            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                counter += 1;
                prog += increment;
                if (counter == stepSize) {
                    self.progressIndictor.progress = prog;
                    counter = 0;
                }
            }];

            // only download if file isn't already here
            BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
            if (!fileExists) {
                NSError *error = nil;

                // jpg or folder check
                if ([[destFile pathExtension] isEqualToString:@"jpg"]) {
                    NSURL *url = [NSURL URLWithString:srcFile];
                    NSData *data = [NSData dataWithContentsOfURL:url
                                                         options:0
                                                           error:&error];

                    [data writeToFile:destFile options:NSDataWritingAtomic error:&error];
                } else {
                    [manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
                }
            }
        }];
    }
}
我可能会建议进行其他改进(例如,实现取消逻辑,而不是查看
stopFlag
),但我试图尽量减少代码更改。我只是利用了一个事实,即可以轻松地替换
dispatch\u async

dispatch_async(dispatchQueue, ^{ ... });
使用
NSOperationQueue
方法
addOperationWithBlock

[operationQueue addOperationWithBlock:^{ ... }];

但是现在我们可以使用一个并发的
NSOperationQueue
,它的
maxConcurrentOperationCount
值为4或5,您会突然享受到一种良好的、受限的并发度。您可能会发现这比按顺序下载文件要快得多。

您的标题将其描述为“泄漏”,但我想知道您是否有理由将其描述为泄漏,而不仅仅是因为您有很多非常大的自动释放对象,在自动释放池耗尽之前不会释放这些对象。我下面的回答假设后者是真实的,但如果你有一些证据表明确实存在泄漏,请告诉我们,但我没有看到任何泄漏。主要的问题似乎是自动释放池没有足够频繁地排空。在阅读了你的答案后,我想你是对的。我假设对象正在被释放,所以不会增加。不应该使用
[[NSData alloc]initWithContentsOfURL:…]
而不是
[NSData dataWithContentsOfURL:…]
来解决这个问题吗?“完成后,您将该变量置为零“他不是
nil
-ing任何东西;它只是超出了范围NewAcct,是的,如果他使用
initWithContentsOfURL
使其成为非自动释放对象,这将大大减少对池的需要,但他确实有其他自动释放对象,并且通过使用手动池,就不必担心哪些是自动释放,哪些不是。但是你是对的,如果他对不使用自动释放对象非常谨慎,那么就不需要手动池。