高效持续地向核心数据导入海量数据
Extremely Massive and Continuous Import to Core Data Efficiently
问题:
当要插入到核心数据的记录数不可预见时,如何释放 NSManagedObjectContext(我猜)使用的内存,以便可以有效地使用内存?
这是我的案例:
我有一个蓝牙设备,每 0.00125 秒(最小间隔,最大情况为 0.002 秒)连续向 iOS 设备发送十二组整数,然后我应该将这些整数与时间戳一起存储到 CoreData 中。
数据Objects和关联:
进程启动时,将创建一个 header 记录(NSManagedObject)作为键,以检索从蓝牙设备接收到的批量数据。 object在整个数据接收期间保持强属性,并在过程结束时从属性(可能设置为nil)中删除。
NSManagedObjectContext 的设计:
三个 ManagedObjectContext 都是单例 object 存储在 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 是使用以下代码创建和存储的:
_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];
}
}
每接收到 1000 个数据,接收到的数据将存储到 managedObjectContext 中。
一旦我停止进程,消耗的内存就会加倍,一直持续到我完全终止应用程序。
处理进程结束的代码如下:
_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 的重置方法将删除所有与上下文关联的托管 objects,并且 "start over" 就像您刚刚创建它一样.
以我的理解,这意味着我只能在整个过程结束时调用此方法。我试图在保存 _backgroundManagedObjectContext 和 _masterManagedObjectContext 之后添加重置功能。但是,内存保持不变。
内存使用说明
对于每 0.002 秒接收一次数据的情况,每 1000 条记录增加 0.5MB 内存保存到 backgroundManagedObjectContext。因此,该应用程序将在 8 分钟的处理时间内消耗大约 150 MB,当进程终止时内存将增加到 320MB,并将保留大约 220MB 的内存使用量。
问题:
当要插入到核心数据的记录数不可预见时,如何释放 NSManagedObjectContext(我猜)使用的内存,以便可以有效地使用内存?
对于一些愚蠢的错误,我很抱歉,因为我在 iOS 中还很陌生。在发布问题之前,我已经尽力搜索了。
感谢您的帮助。
非常感谢。
备注
我已经在不超过 10 分钟的处理时间内尝试了上述案例。但是,实施应该已经延长到 1 小时以上处理时间的情况。我还不知道如何处理这种情况。
编辑 1 修改了显示 ReceivedData 和 Header 关系的代码
编辑 2 更新了@flexaddicted
提到的标准的代码
只是我的建议。也许有人可以有不同的方法。
在这种情况下,我将删除 BackgroundManagedObjectContext,只保留 MasterManagedObjectContext(作为主要对象的父级)。由于您需要低内存配置文件,因此您应该切换到允许您控制应用程序内存占用的机制。因此,我将创建一种开始收集接收数据的缓冲区。当缓冲区达到其限制时,我会将接收到的数据移动到 MasterManagedObjectContext 中,以便将它们保存到持久存储中。这里缓冲区的限制(在我看来是结构向量或对象数组)应该根据应用程序性能进行调整。通过这种方式,您可以直接控制创建的对象。所以,当你完成一堆导入数据时,你可以把它们扔掉(其中一堆是那个限制vector/array)。
否则,您可以尝试以下方法。
@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
你能不能也显示一下你在哪里设置了ReceiveData
和Header
之间的关系?我问这个是因为您可以更改设置这两个实体之间关系的时间。
基于您修改后的代码。
@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 = ...
。换句话说,类 应该以大写字母开头。
问题: 当要插入到核心数据的记录数不可预见时,如何释放 NSManagedObjectContext(我猜)使用的内存,以便可以有效地使用内存?
这是我的案例: 我有一个蓝牙设备,每 0.00125 秒(最小间隔,最大情况为 0.002 秒)连续向 iOS 设备发送十二组整数,然后我应该将这些整数与时间戳一起存储到 CoreData 中。
数据Objects和关联:
进程启动时,将创建一个 header 记录(NSManagedObject)作为键,以检索从蓝牙设备接收到的批量数据。 object在整个数据接收期间保持强属性,并在过程结束时从属性(可能设置为nil)中删除。
NSManagedObjectContext 的设计:
创建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 是使用以下代码创建和存储的:
_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];
}
}
每接收到 1000 个数据,接收到的数据将存储到 managedObjectContext 中。
一旦我停止进程,消耗的内存就会加倍,一直持续到我完全终止应用程序。
处理进程结束的代码如下:
_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 的重置方法将删除所有与上下文关联的托管 objects,并且 "start over" 就像您刚刚创建它一样.
以我的理解,这意味着我只能在整个过程结束时调用此方法。我试图在保存 _backgroundManagedObjectContext 和 _masterManagedObjectContext 之后添加重置功能。但是,内存保持不变。
内存使用说明 对于每 0.002 秒接收一次数据的情况,每 1000 条记录增加 0.5MB 内存保存到 backgroundManagedObjectContext。因此,该应用程序将在 8 分钟的处理时间内消耗大约 150 MB,当进程终止时内存将增加到 320MB,并将保留大约 220MB 的内存使用量。
问题: 当要插入到核心数据的记录数不可预见时,如何释放 NSManagedObjectContext(我猜)使用的内存,以便可以有效地使用内存?
对于一些愚蠢的错误,我很抱歉,因为我在 iOS 中还很陌生。在发布问题之前,我已经尽力搜索了。
感谢您的帮助。 非常感谢。
备注 我已经在不超过 10 分钟的处理时间内尝试了上述案例。但是,实施应该已经延长到 1 小时以上处理时间的情况。我还不知道如何处理这种情况。
编辑 1 修改了显示 ReceivedData 和 Header 关系的代码 编辑 2 更新了@flexaddicted
提到的标准的代码只是我的建议。也许有人可以有不同的方法。
在这种情况下,我将删除 BackgroundManagedObjectContext,只保留 MasterManagedObjectContext(作为主要对象的父级)。由于您需要低内存配置文件,因此您应该切换到允许您控制应用程序内存占用的机制。因此,我将创建一种开始收集接收数据的缓冲区。当缓冲区达到其限制时,我会将接收到的数据移动到 MasterManagedObjectContext 中,以便将它们保存到持久存储中。这里缓冲区的限制(在我看来是结构向量或对象数组)应该根据应用程序性能进行调整。通过这种方式,您可以直接控制创建的对象。所以,当你完成一堆导入数据时,你可以把它们扔掉(其中一堆是那个限制vector/array)。
否则,您可以尝试以下方法。
@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
你能不能也显示一下你在哪里设置了ReceiveData
和Header
之间的关系?我问这个是因为您可以更改设置这两个实体之间关系的时间。
基于您修改后的代码。
@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 = ...
。换句话说,类 应该以大写字母开头。