ios - 在循环中使用 dataWithContentsOfURL 内存泄漏

标签 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];
                   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];
            }
        }
    }
}

如果文件存在,循环将压缩数组并返回主线程。 如果缺少任何文件,下载/写入部分似乎会占用 RAM 并导致触发内存不足警告。需要数千个文件才能完成。

我试过在循环外声明变量,甚至在主线程中执行所有操作以测试这是否导致泄漏。 我尝试使用备用 dataWithContentsOfURL:options:error 调用。 我试过 Instruments,但它真的很慢而且经常崩溃。在崩溃之前,它确实显示分配在缓慢上升、上升、上升。

几天后,我被难住了。

最佳答案

我建议的第一件事是使用 @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];
                }
            }
        }
    }
}

关于ios - 在循环中使用 dataWithContentsOfURL 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23919684/

相关文章:

iphone - 在模拟器上可以,但在iPhone上不行,如何调试?

iphone - NSDictionary initWithContentsOfFile : messes up NSDecimalNumbers, 为什么?

ios - FirebaseFirestore 可以在 iOS 应用扩展中使用吗?

ios - 调试苹果支付消息支付请求无效: <private>

ios - SceneKit 矩阵变换以匹配摄像机角度

ios - UIPageViewController 以编程方式滑动

iphone - 创建两个ios开发人员帐户

iphone - 如何在iphone的objective-c中以编程方式调整图像大小

ios - UITextField 不显示键盘语言选择

objective-c - iOS 方向改变时的非旋转背景图片