将大量数据从核心数据导出到json

时间:2013-05-11 11:45:01

标签: ios objective-c json core-data export

我正在尝试将一些数据从核心数据导出到JSON。虽然记录数量不是特别大(约5000-15000条记录),但我的数据模型很复杂,每条记录中都有大量数据,因此当我导出时,我超出了允许的内存,iOS会杀死我的应用程序。

我目前采取的步骤是:

    1。我有一个方法从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
    }
}

3 个答案:

答案 0 :(得分:2)

查看NSFetchRequest文档。您将看到两个属性:

- (NSUInteger)fetchOffset;
– fetchBatchSize;

使用这两个属性,您可以将返回的NSManagedObject的数量限制为给定的批量大小。

打开您也可以写的流。设置循环以执行获取请求。但是设置批量大小(x),然后在循环代码的末尾更新获取请求的获取偏移量,以便循环的下一次迭代。

myFetchRequestObject.fetchOffset += x;

在开始下一次循环迭代之前,处理将JSON数据写入开放流的一批数据对象。

如果不再返回任何对象或者fetch返回的对象数小于批量大小,则退出循环。

关闭你的小溪。

答案 1 :(得分: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对象,即可访问它们。核心数据包含它自己的内存管理,有故障,所以你不必担心过多的内存使用。

这是一个过程。您实例化访问者对象,然后调用它的开始访问方法传递上面层次结构的根文件夹对象。在该方法中,访问者对象通过在要访问的对象上调用- (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,所以不知道这样的存在是否容易在iOS上使用)。访问者模式确保您只需要实例化一个NSManagedObject来处理层次结构的每个级别(当然,当您实现层次结构遍历逻辑时,实际上不可避免地需要实例化更多),因此这对内存使用情况很轻松。

我已经提供了需要写入输出流的文本字符串的示例。最佳实践将指示使用模板系统而不是直接编写静态分配的字符串。但是,如果您的截止日期紧张,我个人不会担心采用快速而肮脏的方法。

我假设您的文件夹对象包含一个提供一组附加文件夹的文件夹属性。我还假设您的文件夹NSManagedObject类包含一个包含一组单词NSManagedObjects的单词属性。请记住,如果您继续使用Core Data,它将确保您保持较低的内存占用率。

在visitFolder:方法结束时,您可以使用以下逻辑。

  1. 检查文件夹是否包含任何文件夹并依次访问每个文件夹。

  2. 如果它不包含任何文件夹,请检查它是否包含任何单词,如果有,则依次访问每个文件夹。

  3. 注意上面的代码是最简单的构造,用于最小化内存占用。您可能希望通过例如优化它来优化性能仅在超过某个批量大小时才进行自动释放。但是考虑到你所描述的问题,最好首先实现最节省内存的方法。

    如果你有多态的层次结构 - 你自己的:) - 拿出一本书并做一些学习 - 管理它们本身就是一个研究生学位。

    显然,此代码未经测试!

答案 2 :(得分:0)

问题是我在项目架构中启用了Zombie Objects。 出于某种原因,这也延续了发布版本。

将其关闭以解决我的所有问题。

我最终也使用了TheBasicMinds设计模式,因为它是一个很酷的设计模式......