Dropbox Sync API停止下载文件并调用回调方法

时间:2013-12-12 09:59:11

标签: ios dropbox dropbox-api

我的iOS应用程序应该能够从互联网下载大量文件(可以是FTP,REST服务器或任何其他类型的来源,用户可以放置他的文件)。由于一系列优点,我从Dropbox实现了其他可能性的下载。我正在使用Sync API与服务器同步数据。

我遇到的问题如下:有些用户可以在他们的Dropbox文件夹中说11 800个文件和文件夹,app会逐个下载。在下载31-32%的文件之前,一切似乎都很好,大约是3 600 - 3 800个文件。然后,Sync API库停止调用我的回调(观察者方法)并记录消息:

[WARNING] ERR: DROPBOX_ERROR_MISCSYSTEM: cfhttpbinding.c:353: CFHTTP Read Error: (NSPOSIXErrorDomain, 2)
[WARNING] ERR: DROPBOX_ERROR_NETWORK: cfhttpbinding.c:353: CFHTTP Read Error: (kCFErrorDomainCFNetwork, -72000)
[WARNING] ERR: DROPBOX_ERROR_NETWORK: cfhttpbinding.c:353: CFHTTP Read Error: (kCFErrorDomainCFNetwork, -72000)
[WARNING] ERR: DROPBOX_ERROR_NETWORK: cfhttpbinding.c:353: CFHTTP Read Error: (kCFErrorDomainCFNetwork, -72000)

....等等。

我在调试时注意到的是内存使用量增加到600(六百!!!)MB。我正在使用ARC,所以理论上没有内存泄漏是可能的。虽然开发人员仍然可以保留很多强引用,但是什么会阻止内存被释放。当然,我不会忘记保留周期。 我运行“Allocations”仪器,使用xCode内置分析器工具,但仍无法找到并修复此问题。内存使用没有明显的问题。

以下是代码:

- (void) syncImages
{
    if([NSThread isMainThread])
    {
        [self performSelectorInBackground:_cmd withObject:nil];
        return;
    }

    DBPath *path = [[DBPath root] childPath:[SettingsManager dropboxImagesPath]];

    DropboxManagerMetadata *metadata = [DropboxManagerMetadata new];
    metadata.totalAmount = &m_uTotalAmountOfImages;
    metadata.currentAmount = &m_uCurrentImage;
    metadata.updateFiles = m_lstImageUpdateFiles;
    metadata.continueBlock = ^(uint uTotalAmount, uint uCurrentAmount)
    {
        BOOL bContinue = !m_bImagesDownloadPaused;
        return bContinue;
    };
    metadata.shouldProcessFileBlock = ^(NSString * const strRemotePath)
    {
        NSArray *lstPathComponents = [strRemotePath pathComponents];
        // at first check if new file is related to images at all
        // if we have path "/items/some_image.jpg, then we'll have the following path components
        // 1. "/"
        // 2. "items"
        // 3. "some_image.jpg"
        NSString *strRemoteFirstPathComponent = [lstPathComponents objectAtIndex:1];
        BOOL bShouldProcess = [strRemoteFirstPathComponent compare:[SettingsManager dropboxImagesPath] options:NSCaseInsensitiveSearch] == NSOrderedSame;

        return bShouldProcess;
    };
    metadata.willDownloadFileBlock = ^(NSString * const strRemotePath)
    {
        NSMutableString *strLocalPath = [[NSMutableString alloc] initWithString:strRemotePath];
        [strLocalPath replaceOccurrencesOfString:[SettingsManager dropboxImagesPath]
                                      withString:[SettingsManager appItemsDefaultPath]
                                         options:0 range:NSMakeRange(0, [strLocalPath length])];
        [strLocalPath setString:[SettingsManager itemPath:strLocalPath inDirectoryOfType:DirectoryTypeCaches]];

        return strLocalPath;
    };
    metadata.fileProcessedBlock = ^(uint uTotalAmount, uint uCurrentAmount, NSString * const strFilePath)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:cstrNotificationDropboxImagesDownloadProgress object:nil];
    };
    metadata.fileUpdatedBlock = ^(NSString * const strLocalPath)
    {
        NSArray *lstPathComponents = [strLocalPath pathComponents];
        if([lstPathComponents count] < 2) return;

        NSString *strItemNo = [lstPathComponents objectAtIndex:[lstPathComponents count] - 2];
        [[NSNotificationCenter defaultCenter] postNotificationName:cstrNotificationDropboxImageUpdated object:self userInfo:
         [NSDictionary dictionaryWithObject:strItemNo forKey:cstrNotificationDropboxKeyItemNo]];
    };
    metadata.directoryProcessedBlock = ^(DBPath * const dbPath, NSArray * const lstContents)
    {
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSMutableString *strLocalPath = [[NSMutableString alloc] initWithString:[dbPath stringValue]];
        [strLocalPath replaceOccurrencesOfString:[SettingsManager dropboxImagesPath]
                                      withString:[SettingsManager appItemsDefaultPath]
                                         options:0 range:NSMakeRange(0, [strLocalPath length])];
        [strLocalPath setString:[SettingsManager itemPath:strLocalPath inDirectoryOfType:DirectoryTypeCaches]];

        NSMutableArray *lstLocalContents = [[NSMutableArray alloc] initWithArray:[fileManager contentsOfDirectoryAtPath:strLocalPath error:nil]];
        if(lstLocalContents)
        {
            [lstLocalContents filterUsingPredicate:[NSPredicate predicateWithFormat:@"NOT (SELF BEGINSWITH[c] %@)", [ItemImagesAccessor previewImagePrefix]]];
            [lstLocalContents filterUsingPredicate:[NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", lstContents]];
            for(NSString *strFileName in lstLocalContents)
                [self deleteOldImagesAtPath:[strLocalPath stringByAppendingPathComponent:strFileName]];
        }
    };

    [self calculateTotalAmountFilesAtPath:path withMetadata:metadata];
    [[NSNotificationCenter defaultCenter] postNotificationName:cstrNotificationDropboxImagesDownloadProgress object:nil];
    [self downloadContentsOfDirectoryAtPath:path withMetadata:metadata];
}

- (void) calculateTotalAmountFilesAtPath:(DBPath * const)dbPath withMetadata:(DropboxManagerMetadata * const)metadata
{
    DBFilesystem *filesystem = [DBFilesystem sharedFilesystem];
    if(!filesystem) return;

    if(!metadata || !dbPath) return;

    NSArray *lstContents = [filesystem listFolder:dbPath error:nil];

    for(DBFileInfo *info in lstContents)
    {
        if(metadata.shouldProcessFileBlock && !metadata.shouldProcessFileBlock([info.path stringValue])) continue;

        if(metadata.totalAmount)
            *(metadata.totalAmount) = *(metadata.totalAmount) + 1;

        if(info.isFolder)
            [self calculateTotalAmountFilesAtPath:info.path withMetadata:metadata];
    }
}

- (void) downloadContentsOfDirectoryAtPath:(DBPath * const)dbPath withMetadata:(DropboxManagerMetadata * const)metadata
{
    DBFilesystem *filesystem = [DBFilesystem sharedFilesystem];
    if(!filesystem) return;

    if(!dbPath || !metadata) return;

    NSFileManager *fileManager = [NSFileManager defaultManager];

    DBError *error = nil;
    NSArray *lstContents = [filesystem listFolder:dbPath error:&error];
    if(error)
        DDLogWarn(@"error while listing contents at path: %@. code: %u, description: %@", dbPath, [error code], [error localizedDescription]);
    DDLogInfo(@"%u items found in directory: %@", [lstContents count], [dbPath stringValue]);

    float fProgress = 0.f;
    NSMutableArray *lstContentsFromDropbox = [NSMutableArray new];
    for(DBFileInfo *info in lstContents)
    {
        if(metadata.continueBlock && !metadata.continueBlock(*(metadata.totalAmount), *(metadata.currentAmount))) break;

        if(metadata.shouldProcessFileBlock && !metadata.shouldProcessFileBlock([info.path stringValue])) continue;

        if(metadata.currentAmount)
        {
            *(metadata.currentAmount) = *(metadata.currentAmount) + 1;
            if(metadata.totalAmount)
            {
                fProgress = (float)*(metadata.currentAmount) / (float)*(metadata.totalAmount) * 100;
                DDLogInfo(@"processing %u item of %u total amount. progress is %.2f%%", *(metadata.currentAmount), *(metadata.totalAmount), fProgress);
            }
        }

        NSString *strLocalPath = nil;
        if(metadata.willDownloadFileBlock)
            strLocalPath = metadata.willDownloadFileBlock([info.path stringValue]);
        else
            strLocalPath = [[NSMutableString alloc] initWithString:[info.path stringValue]];
        [lstContentsFromDropbox addObject:[strLocalPath lastPathComponent]];

        // check if new item is directory and create directory if needed
        if(info.isFolder)
        {
            if(![fileManager fileExistsAtPath:strLocalPath])
                [fileManager createDirectoryAtPath:strLocalPath withIntermediateDirectories:YES attributes:nil error:nil];

            [self downloadContentsOfDirectoryAtPath:info.path withMetadata:metadata];

            continue;
        }

        DDLogInfo(@"going to open file at path: %@", info.path);
        DBFile *file = [filesystem openFile:info.path error:&error];
        if(error)
            DDLogWarn(@"error while opening file at path: %@. code: %u, description: %@", info.path, [error code], [error localizedDescription]);

        // not nil value for status means, that file at server has some changes
        if(file.newerStatus && [fileManager fileExistsAtPath:strLocalPath])
        {
            // will delete all related preview images if given "strLocalPath" is an image, or simply will delete given file otherwise
            [self deleteOldImagesAtPath:strLocalPath];
        }

        if(file.newerStatus && !file.newerStatus.cached)
        {
            DBFile * __weak fileWeak = file;
            [metadata.updateFiles addObject:fileWeak];
            [file addObserver:self block:^()
             {
                 if(fileWeak.newerStatus.cached || !fileWeak.newerStatus)
                 {
                     DBError *err = nil;
                     [fileWeak update:&err];
                     if(err)
                         DDLogWarn(@"error while updating file at path: %@. code: %u, description: %@", fileWeak.info.path, [err code], [err localizedDescription]);

                     NSData *data = [fileWeak readData:&err];
                     if(err)
                         DDLogWarn(@"error while reading file at path: %@. code: %u, description: %@", fileWeak.info.path, [err code], [err localizedDescription]);
                     [fileManager createFileAtPath:strLocalPath contents:data attributes:nil];

                     [fileWeak removeObserver:self];
                     [fileWeak close];

                     [metadata.updateFiles removeObject:fileWeak];

                     if(metadata.fileUpdatedBlock)
                         metadata.fileUpdatedBlock(strLocalPath);

                     @synchronized(self)
                     {
                         if(![metadata.updateFiles count] && metadata.finishProcessBlock && metadata.isSyncFinished)
                             metadata.finishProcessBlock();
                     }
                 }
             }];
        }
        else
        {
            NSData *data = [file readData:&error]; // get the latest data
            if(error)
                DDLogWarn(@"error while reading file at path: %@. code: %u, description: %@", file.info.path, [error code], [error localizedDescription]);
            // and create file at appropriate directory
            [fileManager createFileAtPath:strLocalPath contents:data attributes:nil];

            [file close];
        }

        if(metadata.totalAmount && metadata.currentAmount)
            metadata.fileProcessedBlock(*(metadata.totalAmount), *(metadata.currentAmount), strLocalPath);
    }

    if(metadata.directoryProcessedBlock)
        metadata.directoryProcessedBlock(dbPath, lstContentsFromDropbox);
}

代码和使用条件的说明:

  1. 方法“calculateTotalAmountOfFilesAtPath:...”和“downloadContentsOfDirectoryAtPath:...”是递归的,但递归永远不会比第1级(0,1,0,退出)更深。例如:/items/1000300/1000300.jpg。 “/ items /”路径作为初始参数给出,“1000300 /”为0级,“1000300.jpg” - 1级。

  2. 1级始终只包含2到3个文件。例如,目录“/ items / 1000300”的内容总是类似于“1000300.jpg”,“1000300a.jpg”,“1000300b.jp”。在0级接收主数据集:初始路径上有许多子目录。例如,“/ items / 1000001”,“/ items / 1000002”,........“/ items / 1007654”。

  3. 有局部变量“NSMutableArray * lstContentsFromDropbox = [NSMutableArray new];”,它保留特定目录内容的路径。它可能会导致0级问题(见第2点),但我试图将其排除在算法之外 - 没有明显改善。

  4. 该错误可能是由Sync API本身的实现引起的吗?它有什么解决方法吗?

    感谢。

    P.S。我更感兴趣的是从Dropbox Sync API中查找有关日志消息的一些信息,而不是如何调试内存问题。

1 个答案:

答案 0 :(得分:0)

问题是由高内存使用(不泄漏)引起的。应用程序必须在循环中执行类似的操作,其中有超过11,000次迭代。即使在堆上分配了一些内存,仍然有其他内存,它在堆栈上分配。此外,我们不应该忘记自动释放的对象,它仍然存在于ARC。

我通过将循环迭代包装到@autoreleasepool块中来解决我的问题。 它与非弧环境相同:即使您不再需要某个对象并释放对该对象的所有引用,如果之前已自动释放,您仍然不知道何时将其从内存中清除。

无论如何,我将重新实现这个算法,因为将11千个对象加载到内存中并将它们保存在列表中以便在循环中处理并不是一个好主意。