【发布时间】:2013-12-30 16:09:14
【问题描述】:
我的 iOS 应用程序应该能够从 Internet 下载大量文件(可以是 FTP、REST 服务器或任何其他类型的来源,用户可以在其中放置文件)。由于一系列优势,我实现了从 Dropbox 下载以及其他可能性。我正在使用 Sync API 与服务器同步数据。
我面临的问题如下:一些用户可以说他们的 Dropbox 文件夹中有 11 800 个文件和文件夹,而应用程序会一个接一个地下载它们。一切似乎都很好,直到下载了 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,所以理论上不可能存在内存泄漏。尽管开发人员仍然可以保留很多强引用,但会阻止内存被释放。当然,我不会忘记保留周期。 我运行“分配”工具,使用 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);
}
代码说明及使用条件:
方法“calculateTotalAmountOfFilesAtPath: ...”和“downloadContentsOfDirectoryAtPath: ...”是递归的,但递归的深度永远不会超过第一级(0、1、0、退出)。例如:/items/1000300/1000300.jpg。 “/items/”路径作为初始参数给出,“1000300/”是级别 0,“1000300.jpg” - 级别 1。
级别 1 始终只包含 2 - 3 个文件。例如,目录“/items/1000300”的内容总是类似于“1000300.jpg”、“1000300a.jpg”、“1000300b.jp”。主数据集在级别 0 接收:初始路径有很多子目录。例如,“/items/1000001”、“/items/1000002”、........“/items/1007654”。
有一个局部变量“NSMutableArray *lstContentsFromDropbox = [NSMutableArray new];”,它保留了特定目录内容的路径。它可能会导致 0 级问题(见第 2 点),但我试图将其从算法中排除 - 没有显着改进。
这个错误是由 Sync API 本身的实现引起的吗?有什么解决办法吗?
谢谢。
附:我更感兴趣的是从 Dropbox Sync API 中查找有关日志消息的信息,而不是如何调试内存问题。
【问题讨论】:
标签: ios dropbox dropbox-api