我正在尝试将一些数据从核心数据导出到JSON。虽然记录计数不是特别大(大约5000-15000条记录),但是我的数据模型很复杂,并且每条记录中都有大量数据,因此当我导出此记录时,我超出了允许的内存,iOS杀死了我的应用程序。
我目前采取的步骤是:
我有一种方法可以从cordata中提取所有数据并将其存储为“NSDictionary”
2.然后,我使用NSOutputStream和NSJSONSerialization将其写入文件。
3.然后我将文件压缩并通过电子邮件发送
我很确定从最大内存角度来看,第2步和第3步很好,因为我可以流式传输数据。但是问题在于,它在步骤1中被杀死了,因为我有效地将所有数据从CD中拉出并放入了内存中,以便可以将其通过NSOutputStream
传递给NSJSONSerialization
。
任何人都知道如何不必将所有内容都拉入内存,但仍要写入单个树JSON文件吗?
更新-更多详细信息
我的数据结构(为简化而简化)如下所示。
考虑到它不仅是平坦的记录集,而且是具有关系的对象的层次结构,我无法弄清楚如何从批处理中将数据从核心数据中提取出来,并馈送到json流化器中,而不是全部存储在内存中以构造json。我上面的第一步实际上是一组递归方法,这些递归方法将数据从核心数据实体中提取出来并构造“NSDictionary”。
Folder {
Folder {
Word {
details type 1
details type 2
}
Word {
details type 1
details type 2
}
}
Folder {
Word {
details type 1
details type 2
}
Word {
details type 1
details type 2
}
}
Word {
details type 1
details type 2
}
}
最佳答案
[已更新为将嵌套文件夹层次结构的低内存串行输出作为嵌套JSON对象文件实现]
现在,您已经提供了更多详细信息,很明显,原始问题陈述缺少足够的详细信息,任何人都无法为您提供答案。您的问题实际上是一个古老的问题,即如何以内存有效的方式遍历层次结构,再加上iOS JSON库非常轻巧,并且不容易支持深度层次结构的流式编写的事实。
最好的方法是使用一种称为访客模式的技术。对于上面显示的每种NSManagedObject类型,请实现一个称为visitor的协议,例如只是每个对象的界面行应如下所示:
@interface Folder : NSManagedObject <Visitable>
@interface Word : NSManagedObject <Visitable>
访客协议应为所有符合协议的对象定义方法调用。
@protocol Visitable <NSObject>
- (void)acceptVisitor:(id<Visitor>)visitor;
@end
您将定义一个访问者对象,该对象本身实现一个访问者协议。
@protocol Visitor <NSObject>
- (void)visitFolder:(Folder*)folder;
- (void)visitWord:(Word*)word;
@end
@interface JSONVisitor : NSObject <Visitor>
@property (nonatomic, strong) NSURL *streamURL;
- (void)startVisiting:(id<Visitable>)visitableObject;
@end
@implementation JSONVisitor
@property (nonatomic, strong) NSOutputStream *outputStream;
- (void)startVisiting:(id<Visitable>)visitableObject
{
if ([visitableObject respondsToSelector:@selector(acceptVisitor:)]
{
if (_outputStream == nil)
{
// more code required set up your output stream
// specifically as a JSON output stream.
// add code to either set the stream URL here,
// or set it when the visitor object is instantiated.
_outputStream = [NSOutputStream outputStreamWithURL:_streamURL append:YES];
}
[_outputStream open];
// Note 1a Bypass Apple JSON API which doesn't support
// writing of partial objects (doing so is very easy anyway).
// Write opening root object fragment text string to stream
// such as:
// {
// "$schema" : "http://myschema.com/draft-01/schema#Folder1",
// "name" : "Folder export",
// "created" : "2013-07-16T19:20:30.45+01:00",
// "Folders" : [
[visitableObject acceptVisitor:self];
// Note 1b write closing JSON root object
// e.g.
// ]
// }
[_outputStream close];
}
}
- (void)visitFolder:(Folder*)folder
{
// Note 2a Bypass Apple JSON API which doesn't appear to support
// writing of partial objects (Writing JSON is very easy anyway).
// This next step would be best done with a proper templating system,
// but for simplicity of illustration I'm suggesting writing out raw
// JSON object text fragments.
// Write opening JSON Folder object fragment text string to stream
// e.g.
// "Folder" : {
if ([folder.folders count] > 1) {
// Write opening folder array fragment to stream e.g.
// "Folders" : [
// loop through folder member NSManagedObjects here
// (note defensive checks for nulls not included).
NSUInteger count = 0;
for (Folder *nestedFolder in folder.folders)
{
if (count > 0) // print comma to output stream
[nestedFolder acceptVisitor:self];
count++;
}
// write closing folders array to stream
// ]
}
if ([folder.words count] > 1) {
// Write opening words array fragment to stream e.g.
// "Words" : [
// loop through Word member NSManagedObjects here
// (note defensive checks for nulls not included).
NSUInteger count = 0;
for (Word *nestedWord in folder.words)
{
if (count > 0) // print comma to output stream
[nestedFolder acceptVisitor:self];
count++;
}
// write closing Words array to stream
// ]
}
// Print closing Folder object brace to stream (should only be followed
// a comma if there are more members in the folder this object is contained by)
// e.g.
// },
// Note 2b Next object determination code here.
}
- (void)visitWord:(Word*)word
{
// Write to JSON stream
[NSJSONSerialization writeJSONObject:word toStream:_outputStream options: NSJSONWritingPrettyPrinted error:nil];
}
@end
该对象能够“访问”层次结构中的每个对象并对其进行一些工作(在您的情况下,将其写入JSON流)。请注意,您不需要先提取字典。您只需直接使用Core Data对象,即可访问它们。核心数据包含它自己的内存管理,但有故障,因此您不必担心过多的内存使用。
这是过程。您实例化visitor对象,然后调用它的开始访问方法,并将其传递到上面层次结构的根Folder对象中。在该方法中,访问者对象通过在要访问的对象上调用
- (void)acceptVisitor:(id<Visitor>)visitor
来“敲打要访问的第一个对象的门”。然后,根文件夹通过在访问者对象上调用与其自己的对象类型匹配的方法来“欢迎访问者”,例如:- (void)acceptVisitor:(id<Visitor>)visitor
{
if ([visitor respondsToSelector:@selector(visitFolder:)]) {
[visitor visitFolder:self];
}
}
依次调用访问者对象上的visitFolder:方法,该对象打开流,将对象写为JSON,然后关闭流。这是重要的。这种模式一开始可能看起来很复杂,但是我保证,如果您正在使用层次结构,则一旦实现,便会发现它功能强大且易于管理。
为了支持深度层次结构的低内存串行输出,建议您将自己的JSON Folder对象写入输出流。由于JSON非常简单,因此比起初看起来要容易得多。另一种选择是寻找一个JSON库,该库支持嵌套对象的低内存序列化写入(我没有使用过JSON,所以不知道是否存在JSON,并且在iOS上易于使用)。访客模式可确保您不需要为每个层次结构的每个实例实例化一个以上的NSManagedObject(当然,在实现层次结构遍历逻辑时,当然将不可避免地需要实例化更多实例),因此这可以减少内存使用量。
我已经给出了需要写入输出流的文本字符串的示例。最佳实践将要求为此使用模板系统,而不是直接编写静态分配的字符串。但是个人而言,如果您的截止日期很紧,我不会担心采用快速而肮脏的方法。
我假设您的文件夹对象包含一个folders属性,该属性提供了一组其他文件夹。我还假定您的Folders NSManagedObject类包含一个words属性,该属性包含一组Words NSManagedObjects。请记住,如果您继续使用Core Data,它将在确保您保持较低的内存占用量后进行维护。
在visitFolder:方法的结尾,您可以使用以下逻辑。
请注意,上面的代码是用于最小化内存占用的最简单的构造。您可能想通过以下方式优化其性能:仅在超过一定批次大小时才执行自动发布。但是,鉴于您所描述的问题,最好首先实现内存效率最高的方法。
如果您有多态层次结构-您可以自己创建:)-拿出一本书并做一些研究-管理它们本身就是一个研究生学位。
显然,此代码未经测试!
关于ios - 将大量数据从核心数据导出到json,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16496722/