我正在通过仪器运行我的应用程序,并用大量数据强调它。仪器测试很好,但压力测试是我遇到问题的地方。在没有深入细节的情况下,我正在为我的应用程序提供越来越多的Core Data
事件,以便在MKMapView
实例上推断数据,制作图表和显示位置。我开始小规模并增加到56000个事件,它处理得很好,没有任何泄漏或内存警告(我为它处理它而感到非常自豪)。
我的应用实现了Dropbox API,允许上传和下载模板和数据以进行同步。从我的应用上传的文件从Core Data
转换为NSDictionary
,然后转换为NSData
。我为数据创建了一个临时文件夹,然后将该文件上传到Dropbox,这通常正常工作。如果我尝试使用56000个事件上传我的数据文件,那么它会崩溃。我已经记录并观察数据是否被转换。它到达最后一个没有问题的事件,但当它应该开始上传到Dropbox时,应用程序崩溃了,我不能为我的生活找出原因。我看到我的日志中弹出了内存警告。通常情况下,它会变为Level = 1,Level = 2,Level = 1,Level = 2,然后崩溃,这让我感到困惑,因为它永远不会达到Level = 3.
我发现的大部分信息都在我的编辑中。以下是一些相关代码:
- (void)uploadSurveys:(NSDictionary *)dict {
NSArray *templateArray = [dict objectForKey:@"templates"];
NSArray *dataArray = [dict objectForKey:@"data"];
NSString *filename;
NSLog(@"upload called");
if ([templateArray count] || [dataArray count]) {
if ([templateArray count]) {
// irrelevent code;
}
if ([dataArray count]) {
SurveyData *survey;
for (int i = 0; i < [dataArray count]; i++) {
BOOL matchExists = NO;
// ...... code to make sure no file exists in dropbox folder and creates new version if necessary;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [self convertSurvey:survey];
dispatch_async(dispatch_get_main_queue(), ^{
[self uploadData:data withFilename:filename];
NSLog(@"converted and uploading");
});
});
}
}
}
[self convertSurvey:survey]
只是将我的Core Data
对象转换为NSData
。
- (void)uploadData:(NSData *)data withFilename:(NSString *)filename {
NSFileManager *manager = [NSFileManager defaultManager];
NSString *pathComponent = [NSString stringWithFormat:@"tempData.%@", filename];
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:pathComponent];
if ([manager createFileAtPath:path contents:data attributes:nil]) {
[self.restClient uploadFile:filename toPath:[NSString stringWithFormat:@"/%@", currentSearch] fromPath:path];
NSLog(@"uploading data");
}
}
任何帮助都会得到很多帮助,我提前非常感谢你。我只想弄清楚我是在采取错误的方法来处理大文件,还是只是不允许这样做。如果我必须拆分文件,那很好,但我想知道发生了什么阻止我的应用程序在我尝试解决之前阻止我的应用程序执行此操作。再次感谢你。
更新:由于此问题现在是我的应用程序发布的唯一障碍,我正在为这个问题添加一笔赏金,希望能得到解决方案或解决方法。这将持续一个星期,在此之后给定时间我很可能只是在上传时拆分文件以确保未达到这个明显的大小限制。这种方法并不理想,这就是为什么一个更好的解决方案非常受欢迎的原因,但如果这不能带来更方便的话,那就是我的备份计划。
编辑:看来NSTemporaryDirectory
根本不参与其中。这是新情况。正如您在上面的代码中看到的那样,在辅助线程中调用NSData *data = [self convertSurvey:survey];
(这不是问题)。我一直在记录创建的对象,并且知道它们已经到达最后一个,但从未想过检查并查看是否返回了NSData
文件。事实证明,事实并非如此。简而言之,我将所有Core Data对象转换为数组并将它们放入字典中(仅用于要转换的相关调查/数据)。这确实有效,并且创建了字典。然后我使用NSData
创建NSData *data = [NSKeyedArchiver archivedDataWithRootObject:d];
文件,其中d
是我的字典。在此之后,我致电return data;
设置NSData *data = [self convertSurvey:survey];
的值。在这种情况下,NSData
或NSKeyedArchiver
似乎有问题。根据Apple文档:
使用32位Cocoa,数据的大小受理论上的2GB限制(实际上,因为内存将被其他对象使用,此限制会更小);使用64位Cocoa,数据的大小受理论限制约为8EB(实际上,限制不应该是一个因素)。
我已经以小增量检查了文件大小,以查看故障发生的位置。我已成功获得48.2MB的数据,但不是51.5MB,这让我相信问题发生在50MB左右,远低于NSData
的理论极限(除非iOS和OS X之间存在差异)尊重)。
希望这些新信息有助于解决这个问题
答案 0 :(得分:2)
NSData的2 GB限制在iOS上完全是理论上的,即使iPhone 4只有512 MB的RAM而iOS(与Mac OS X不同)也无法交换,所以如果你的物理内存已满,你就会崩溃(或者你的app在此之前被终止。
单独50 MB NSData
对象已经非常大并且它不是您在内存中的唯一对象 - 假设您将数据从Core Data转换为字典表示然后转换为NSData
,你可能消耗至少两倍的内存(可能更多)。系统和其他应用程序也需要RAM,因此您可能已达到限制。
尝试在Instruments中运行您的应用,看看您实际消耗了多少内存。
为减少峰值内存使用量,您有几个选项在很大程度上取决于您的数据模型:
正如Jason Foreman在他的回答中建议的那样,尽量避免将整个文件同时存入内存。使用NSFileHandle
,您可以将数据块写入文件,而无需一次将整个数据存储在内存中。当然,这需要您相应地准备数据,以便可以将其拆分为块。更高级别的方法可能是将数据序列化为XML格式,您可以将其作为流写出。如果您的数据格式非常简单,CSV之类的内容也可能有效。
请勿使用NSData
上传到Dropbox。将数据写入文件(参见上文)并将Dropbox SDK指向该文件。 Dropbox SDK非常容易实现(DBRestClient
有uploadFile:toPath:fromPath:
方法)。
如果您的数据模型难以采用流媒体方法,请尝试将数据细分为更易于管理的部分。然后,您可以使用旧的序列化字典方法,只需使用多个文件。
小心Core Data的内存使用情况。如果可能,尝试使用refreshObject:mergeChanges:
重新排除对象故障,以打破数据中的循环引用(有关详细信息,请参阅Core Data Programming Guide)。
在长时间运行的循环中避免使用自动释放池,或者创建一个单独的NSAutoreleasePool
,在循环的每次迭代中都会耗尽。
答案 1 :(得分:1)
解决此类内存压力的方法是使用流构建API,既可以将转换后的数据写入磁盘上的文件,也可以将数据上传到Web服务。
在转换过程中,您可以使用NSOutputStream
将大块数据写入文件,以避免一次在内存中保留大量数据。然后,NSMutableURLRequest
可以接受正文NSStream
而不是NSData
,因此您应该创建一个NSInputStream
来从磁盘读取文件并上传。< / p>
以这种方式使用流将确保您永远不会加载50多MB的数据,并且应该避免您看到的内存警告。