极其庞大且持续有效地导入核心数据

时间:2015-02-18 14:04:20

标签: objective-c core-data memory-management nsmanagedobjectcontext

问题:   当插入到Core Data的记录数量无法预测时,如何释放NSManagedObjectContext使用的内存(我猜),这样可以有效地使用内存?

以下是我的理由:   我有一个蓝牙设备,每隔0.00125秒就会连续向iOS设备发送12组整数(最小间隔,最大情况为0.002秒),然后我应该将这些整数存储到带有时间戳的CoreData中。

数据对象和关联:

enter image description here

当进程开始时,创建标题记录(NSManagedObject)作为从蓝牙设备检索批量接收数据的密钥。在整个数据接收期间,对象将作为强属性保留,并在进程结束时从属性中删除(可能设置为nil)。

设计NSManagedObjectContext:

enter image description here 所有三个ManagedObjectContext都是存储在AppDelegate

中的单例对象

下面列出了创建ManagedObjectContext的代码:

- (NSManagedObjectContext *)masterManagedObjectContext {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
    if (_masterManagedObjectContext != nil) {
        return _masterManagedObjectContext;
    }
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (!coordinator) {
        return nil;
    }
    _masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
    [_masterManagedObjectContext setUndoManager:nil];
    return _masterManagedObjectContext;
}
-(NSManagedObjectContext*) backgroundManagedObjectContext{
    if(_backgroundManagedObjectContext != nil){
        return _backgroundManagedObjectContext;
    }
    _backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    [_backgroundManagedObjectContext setUndoManager:nil];
    [_backgroundManagedObjectContext setParentContext:[self masterManagedObjectContext]];
    return _backgroundManagedObjectContext;
}
-(NSManagedObjectContext*) mainManagedObjectContext{
    if(_mainManagedObjectContext !=nil){
        return _mainManagedObjectContext;
    }
    _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_mainManagedObjectContext setUndoManager:nil];
    [_mainManagedObjectContext setParentContext:[self masterManagedObjectContext]];
    return _mainManagedObjectContext;
}

导入在backgroundManagedObjectContext中处理。 使用以下代码创建和存储标题:

_header = [NSEntityDescription insertNewObjectForEntityForName:@"Header" inManagedObjectContext:_backgroundManagedObjectContext];
_header.startTime = [NSDate date];
NSError *error;
BOOL success = [_backgroundManagedObjectContext save:&error];

当蓝牙设备触发方法时,使用以下代码创建和存储接收数据:

@autoreleasepool {    
    ReceivedData* data = [NSEntityDescription insertNewObjectForEntityForName:@"ReceivedData" inManagedObjectContext:_backgroundManagedObjectContext];
    //Data is set here
    [_header addFk_header_many_dataObject:data];
    currentCount ++;
    if(currentCount >=1000){
        currentCount = 0;
        NSError *error;
        BOOL success = [_backgroundManagedObjectContext save:&error];
    }
}

接收的数据将被存储到managedObjectContext中,每1000个数据被接收。

如果我停止了这个过程,消耗的内存会翻倍,并持续到我完全终止该应用程序。

下面列出了处理流程结束的代码:

_header.endTime = [NSDate date];
_header = nil;
NSError *error;
BOOL success = [_backgroundManagedObjectContext save:&error];
[_masterManagedObjectContext performBlock:^{
   NSError* mastererror;
   BOOL mastersuccess = [_masterManagedObjectContext save:&mastererror];
}];

问题: 正如Core Data Performance by Apple所提到的,使用NSManagedObjectContext的reset方法的将删除与上下文关联的所有托管对象,并且"重新开始"好像你刚刚创建它

根据我的理解,这意味着我只能在整个过程结束时调用此方法。我已经尝试在保存_backgroundManagedObjectContext和_masterManagedObjectContext之后添加重置函数。但是,记忆保持不变。

内存使用情况的说明 对于每0.002秒接收一次数据的情况,每1000条记录增加的0.5MB内存将保存到backgroundManagedObjectContext。因此,应用程序将消耗大约150 MB,持续8分钟的处理时间,当此进程终止时内存将增加到320MB,并将保留大约220MB的内存使用量。

问题:   当插入到Core Data的记录数量无法预测时,如何释放NSManagedObjectContext使用的内存(我猜),这样可以有效地使用内存?

对于一些愚蠢的错误感到抱歉,因为我在iOS上很新。在发布问题之前,我已尽力搜索。

感谢您的帮助。 非常感谢你。

说明 我在不超过10分钟的处理时间内尝试了上述情况。但是,实施应该延长到超过1小时的处理时间。我仍然不知道如何处理这种情况。

编辑1 修改了显示ReceivedData和Header关系的代码 EDIT 2 更新了@flexaddicted

提及的标准代码

1 个答案:

答案 0 :(得分:0)

只是我的建议。也许有人可能有不同的方法。

在这种情况下,我将消除BackgroundManagedObjectContext,我只留下MasterManagedObjectContext(作为主要的父级)。因为,您需要一个低内存配置文件,您应该切换到允许您控制应用程序的内存占用的机制。所以,我会创建一种开始收集接收数据的缓冲区。当缓冲区达到其限制时,我会将接收数据移动到MasterManagedObjectContext,以便将它们保存到持久存储中。这里应该根据应用程序性能调整缓冲区的限制(结构的向量或对象数组)。通过这种方式,您可以直接控制所创建的对象。所以,每当你完成一堆导入的数据(其中束是该向量/数组的限制)时,你可以扔掉它们。

否则,您可以尝试以下方法。

@autoreleasepool {    

    NSMutableArray *temporary = [NSMutableArray array];

    ReceivedData* data = [NSEntityDescription insertNewObjectForEntityForName:@"ReceivedData" inManagedObjectContext:_backgroundManagedObjectContext];
    // Data is set here

    // Let temporary to hold a reference of the data object 
    [temporary addObject:data];
    currentCount ++;
    if(currentCount >=1000){
        currentCount = 0;
        NSError *error;
        BOOL success = [_backgroundManagedObjectContext save:&error];

        for(NSManagedObject *object in temporary) {
            [_backgroundManagedObjectContext refreshObject:object mergeChanges:NO];
        }
        [temporary removeAllObjects];
    }
}

更新1

您是否还可以显示您在ReceiveDataHeader之间设置关系的位置?我之所以这样问,是因为你可以改变设置这两个实体之间关系的时间。

根据您修改的代码。

@autoreleasepool {    
    receivedData* data = [NSEntityDescription insertNewObjectForEntityForName:@"ReceivedData" inManagedObjectContext:_backgroundManagedObjectContext];
    //Data is set here

    [_header addFk_header_many_dataObject:data];
    currentCount ++;
    if(currentCount >=1000){
        currentCount = 0;
        NSError *error;
        BOOL success = [_backgroundManagedObjectContext save:&error];
    }
}

如果您能够在主队列上预测此关联(我想您需要将该属性设置为可选属性),您可以执行以下操作:

@autoreleasepool {    
    ReceivedData* data = [NSEntityDescription insertNewObjectForEntityForName:@"ReceivedData" inManagedObjectContext:_backgroundManagedObjectContext];
    // Data is set here

    // Move it later
    //[_header addFk_header_many_dataObject:data];

    currentCount ++;
    if(currentCount >=1000){
        currentCount = 0;
        NSError *error;
        BOOL success = [_backgroundManagedObjectContext save:&error];
        [_backgroundManagedObjectContext reset];
    }
}

P.S。 receiveData *data = ...应为ReceiveData *data = ...。换句话说,课程应以大写字母开头。