3

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

我目前采取的步骤是:

    1.我有一种方法可以从cordata中提取所有数据并将其存储为`NSDictionary`
    2. 然后我使用 `NSOutputStream` 和 `NSJSONSerialization` 将其写入文件
    3. 然后我将文件压缩并通过电子邮件发送

从最大内存的角度来看,当我流式传输数据时,我很确定第 2 步和第 3 步没问题。但问题是它在步骤 1 中被杀死,因为我有效地将所有数据从 CD 中取出并将其放入内存中,这样我就可以将其传递NSOutputStreamNSJSONSerialization.

任何人都知道如何不必将所有内容都拉入内存,但仍然写入单个树 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
    }
}
4

3 回答 3

4

[更新以实现嵌套文件夹层次结构的低内存串行输出作为嵌套 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<Visitor>)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. 如果它不再包含文件夹,请检查它是否包含任何单词,如果包含,则依次访问每个文件夹。

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

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

显然这段代码未经测试!

于 2013-05-12T09:45:30.243 回答
2

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

- (NSUInteger)fetchOffset;
– fetchBatchSize;

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

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

myFetchRequestObject.fetchOffset += x;

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

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

关闭您的信息流。

于 2013-05-11T12:46:16.350 回答
0

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

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

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

于 2014-01-04T13:22:27.320 回答