【问题标题】:Exporting large amounts of data from coredata to json将大量数据从coredata导出到json
【发布时间】:2013-05-05 23:48:10
【问题描述】:

我正在尝试将一些数据从核心数据导出到 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
    }
}

【问题讨论】:

  • 您是否为此使用了不同的线程?分享一些其他细节。谢谢。
  • 是的,它位于与主线程不同的线程上。我使用我的主线程为用户更新进度

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


【解决方案1】:

[更新以实现嵌套文件夹层次结构的低内存串行输出作为嵌套 JSON 对象文件]

现在您提供了更多详细信息,很明显原始问题陈述缺乏足够的详细信息,任何人都无法为您提供答案。您的问题实际上是一个古老的问题,即如何以内存有效的方式遍历层次结构,再加上 iOS JSON 库非常轻量级并且不容易支持深度层次结构的流式写入)。

最好的方法是使用一种称为访问者模式的技术。对于上面显示的每个 NSManagedObject 类型,实现一个称为访问者的协议,例如只是每个对象的接口线应该是这样的:

@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 对象,使它们可访问。 Core Data 包含它自己的内存管理,有故障,所以你不必担心过多的内存使用。

这就是过程。您实例化访问者对象,然后调用它的开始访问方法,传入上面层次结构的根文件夹对象。在该方法中,访问者对象通过在要访问的对象上调用- (void)acceptVisitor:(id&lt;Visitor&gt;)visitor 来“敲门”第一个要访问的对象。根文件夹然后通过调用与它自己的对象类型匹配的访问者对象上的方法来“欢迎访问者”,例如:

- (void)acceptVisitor:(id<Visitor>)visitor
{
    if ([visitor respondsToSelector:@selector(visitFolder:)]) {
        [visitor visitFolder:self];
    }
}

这反过来调用访问者对象上的 visitFolder: 方法打开流将对象写入 JSON 并关闭流。这是重要的事情。这种模式一开始可能看起来很复杂,但我保证,如果您使用层次结构,一旦实现它,您会发现它功能强大且易于管理。

为了支持深层次的低内存串行输出,我建议您将自己的 JSON 文件夹对象写入输出流。由于 JSON 非常简单,这比它最初看起来要容易得多。另一种方法是寻找一个支持嵌套对象的低内存序列化写入的 JSON 库(我没有使用太多 JSON,所以不知道这种情况是否存在并且易于在 iOS 上使用)。访问者模式确保您需要为层次结构的每一级实例化不超过一个 NSManagedObject(当然,当您实现层次结构遍历逻辑时,将不可避免地需要实例化更多对象),因此这对内存使用量很轻。

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

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

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

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

  2. 如果没有文件夹,检查是否有Word,如果有,依次访问。

请注意,上面的代码是最小化内存占用的最简单结构。您可能希望通过例如优化它的性能。仅在超过某个批量大小时才进行自动发布。但是,鉴于您描述的问题,最好先实现内存效率最高的方法。

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

显然这段代码未经测试!

【讨论】:

  • 这写得很好,这是一种我不知道的模式,从可维护性的角度来看,它对于遍历层次结构是有意义的。如果我正在遍历层次结构并将每个对象转储到普通文件中,这也是有意义的。但是,如果我像这样流式传输到 json 文件,我会丢失层次结构的 json 关系,而只是获取一个一个接一个地列出的单独 json 对象的列表,而不是在语法 json 层次结构中结构化。
  • 它仍然很好,这说明它是正确的方法。您遇到的问题是 iOS 库中的标准 JSON 类相对较轻,不支持子对象成员和值创建。您可以使用另一个库或推出自己的解决方案。就我个人而言,我会自己动手。这比起初想象的要容易。我将修改我的答案以显示如何完成。
  • 抱歉得赶时间。在移动中。答案需要做更多的工作。希望它有帮助。请记住在 visitFolder: 方法中,您可以通过在嵌套对象上调用 acceptVisitor: 来嵌套调用。这是一种功能强大且易于遵循的模式。
  • 是的,我的问题不在于层次结构遍历,而是将核心数据中的内容以最小的峰值内存使用量获取到 json 中。从 json 文件中读取要容易一些,因为我可以使用内存映射的 nsdata 读取它,或者使用 NSJSONSerialization 从文件中流式传输它,这样可以避免内存不足,但是在编写 json 时,我不知道如何避免完整json 层次结构在内存中的某个时刻。我仍然看到 2GB+ json 文件漂浮在周围 - 必须有一种方法,无需编写自定义“解析和搜索 json 文件中的位置并插入”
  • 再看一遍。我所做的更改将为您提供具有最小内存占用的完整层次结构。您需要手动写出 JSON 文件夹对象的左大括号和右大括号,以便流程进行流式传输,但这很容易做到(JSON 非常简单)。并且仍然很容易使用 iOS JSON 库来写出非嵌套文件夹成员。当我有更多时间时,我会用进一步的插图来修改它。
【解决方案2】:

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

- (NSUInteger)fetchOffset;
– fetchBatchSize;

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

打开一个你也可以写的流。设置一个循环来执行一个获取请求。但是设置一个batch size (x),然后在循环代码结束时更新获取请求的获取偏移量,用于循环的下一次迭代。

myFetchRequestObject.fetchOffset += x;

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

当没有更多对象返回或 fetch 返回的对象数量小于批量大小时,退出循环。

关闭您的信息流。

【讨论】:

  • 顺便说一句,不要忘记用 @autorelease{} 括住循环的内容,这样您就可以随时回收内存。
  • 我的所有方法都包含在 autoreleasepool 中。还有所有带有大 for 循环的东西也包含在 autoreleasepool 中。这种方法用于平面数据,即记录列表。但是你如何让它与分层数据一起工作?如何让下一批插入到 json 数据中的正确位置,而不仅仅是在文件末尾?我已经用有关我的数据的更多信息更新了我的问题。
【解决方案3】:

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

关闭它可以解决我的所有问题。

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

【讨论】:

    猜你喜欢
    • 2019-10-29
    • 2014-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 2011-03-02
    • 1970-01-01
    • 2013-10-06
    相关资源
    最近更新 更多