I have a screen that holds a UITableView, in this screen I have an array of NSManagedObjects. It's working just fine, but as I try move to another screen (click on a specific cell, and push a new screen), then return to the same UITableView screen, all the objects got lost.
What does it means? I try to print the array of the NSManagedObjects and it's fine, all the objects there, but as I print the description of each object, I get nil from all the object attributes.
Someone knows whats the cause of it? I don't know why but it worked just fine 12 hours ago, but now it's all messed up and I don't have a clue what have I done.
Thanks in advance!
Save method:
- (void)saveContext {
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
NSError *error = nil;
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
else {
NSLog(#"Context saved!");
}
}
}
This is how I save the objects:
NSDictionary *response = responseObject;
if ([[response valueForKey:#"status"] rangeOfString:#"ok"].location != NSNotFound)
{
NSArray *data = [response objectForKey:#"data"];
if (data.count != 0)
{
if (page.integerValue == 0) {
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeCuisine"];
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeCategory"];
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeDish"];
}
NSMutableArray *homePageObjects = [[NSMutableArray alloc] initWithCapacity:data.count];
for (NSDictionary *object in data)
{
NSNumber *type = [object objectForKey:#"type"];
switch (type.integerValue) {
case 1:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeCuisine *homeCuisine = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeCuisine" inManagedObjectContext:context];
NSInteger cuisineId = [[content valueForKey:#"cuisine_id"] integerValue];
homeCuisine.cuisine = [self gCuisineWithCuisineId:[NSNumber numberWithInteger:cuisineId]];
NSInteger count = [[content valueForKey:#"count"] integerValue];
homeCuisine.count = [NSNumber numberWithInteger:count];
homeCuisine.type = type;
[homePageObjects addObject:homeCuisine];
}
break;
case 2:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeCategory *homeCategory = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeCategory" inManagedObjectContext:context];
NSInteger categoryId = [[content valueForKey:#"category_id"] integerValue];
homeCategory.category = [self gCategoryWithCategoryId:[NSNumber numberWithInteger:categoryId]];
NSInteger count = [[content valueForKey:#"count"] integerValue];
homeCategory.count = [NSNumber numberWithInteger:count];
homeCategory.type = type;
[homePageObjects addObject:homeCategory];
}
break;
case 3:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeDish *homeDish = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeDish" inManagedObjectContext:context];
homeDish.dishId = [self gInt:content forKey:#"dish_id"];
homeDish.headline = [AppUtils checkForEmptyValue:[content valueForKey:#"title"]];
homeDish.text = [AppUtils checkForEmptyValue:[content valueForKey:#"description"]];
homeDish.cuisineId = [self gInt:content forKey:#"cuisine_id"];
homeDish.cuisine = [self gCuisineWithCuisineId:homeDish.cuisineId];
homeDish.creationDate = [AppUtils checkForEmptyValue:[content valueForKey:#"creation_time"]];
homeDish.userId = [self gInt:content forKey:#"user_id"];
homeDish.longitude = [self gDouble:content forKey:#"lng"];
homeDish.latitude = [self gDouble:content forKey:#"lat"];
homeDish.lastPromoteDate = [AppUtils checkForEmptyValue:[content valueForKey:#"last_promote_time"]];
homeDish.price = [self gInt:content forKey:#"price"];
homeDish.currency = [AppUtils checkForEmptyValue:[content valueForKey:#"currency"]];
homeDish.countryName = [AppUtils checkForEmptyValue:[content valueForKey:#"country_name"]];
homeDish.baseCurrency = [self gFloat:content forKey:#"base_currency"];
homeDish.exchangeRate = [self gFloat:content forKey:#"exchange_rate"];
homeDish.countryIsoCode = [AppUtils checkForEmptyValue:[content valueForKey:#"country_iso_code"]];
homeDish.mainPhoto = [AppUtils checkForEmptyValue:[content valueForKey:#"main_photo"]];
homeDish.like = [self gLikeWithDishId:homeDish.dishId];
homeDish.profileImageURL = [AppUtils checkForEmptyValue:[content valueForKey:#"profile_img_url"]];
homeDish.likeCount = [self gInt:content forKey:#"likes"];
homeDish.type = type;
[homePageObjects addObject:homeDish];
}
break;
default:
break;
}
}
// ##log -- Save data to core data and device
//
//
[[MainDb sharedDb] saveContext];
if (success) {
success(operation, homePageObjects);
}
}
}
Seriously, you should consider refactoring using a NSFetchedResultsController. Start from the template provided in Xcode (New Project -> Master/Detail -> check Core Data, the code is in MasterViewController.m).
I strongly discourage loading Core Data objects into an array to be displayed in a table view. Your problem is typical for such a setup, and you will run into memory and performance issues eventually as well.
Related
My first post here, getting straight to the point.
I'm facing a problem and I'm not sure whether is with saving of data or fetching it.
Here are my codes where I tried to save the data in my Entity, "Sequence". The entity consists of 2 attributes "seqForWk1CD" & "seqForWk2CD".
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription
insertNewObjectForEntityForName:#"Sequence"
inManagedObjectContext:context];
// some other code
seqEntity.seqForWk1CD = arrForWk1;
seqEntity.seqForWk2CD = arrForWk2;
NSLog(#"%#", arrForWk1);
NSLog(#"%#", arrForWk2);
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
When printed, the arrays will always display the contents of the array.
This is where I try to fetch the data.
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sequence" inManagedObjectContext:context];
[request setEntity:entity];
NSError *error;
if(![context save:&error]){
NSLog(#"Error fetching. %# %#", error, [error localizedDescription]);
}
NSUInteger count = [context countForFetchRequest:request error:&error];
if(count != 0){
NSArray *fetchObj = [context executeFetchRequest:request error:&error];
NSManagedObject *sequence = (NSManagedObject *)[fetchObj objectAtIndex:0];
NSLog(#"1 - %#", sequence);
arrForWk1 = [sequence valueForKey:#"seqForWk1CD"];
NSLog(#"%#", arrForWk1);
arrForWk2 = [sequence valueForKey:#"seqForWk2CD"];
NSLog(#"%#", arrForWk2);
}
The problem comes when I restart the application. The arrays either show (null) for both arrays or it shows the contents of both of the arrays. The if statement for if(![context save:&error]) never gets triggered. Subclasses of NSManagedObject for the entity has already been added.I've also tried declaring the AppDelegate in #interface and forced to save the context immediately by doing [delegate saveContext];.Here is the method where the saving happens. "check" is initialized to 0 at the viewDidLoad method. Both "arrForWk1" & "arrForWk2" are declared at #interface.
- (IBAction)randomizeSequence:(UIButton *)sender {
NSMutableArray *storeArray = [[NSMutableArray alloc] init];
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Sequence" inManagedObjectContext:context];
BOOL record = NO;
int x;
for (int i=0; [storeArray count] < 9; i++) //Loop for generate different random values
{
x = 1 + arc4random() % 9;//generating random number
if(i==0)//for first time
{
[storeArray addObject:[NSNumber numberWithInt:x]];
}
else
{
for (int j=0; j<= [storeArray count]-1; j++)
{
if (x ==[[storeArray objectAtIndex:j] intValue])
record = YES;
}
if (record == YES)
{
record = NO;
}
else
{
[storeArray addObject:[NSNumber numberWithInt:x]];
}
}
}
check++;
if(check == 1 ) {
arrForWk1 = storeArray;
[self.wk1Seq reloadData];
}
else if(check == 2) {
arrForWk2 = storeArray;
seqEntity.seqForWk1CD = arrForWk1;
seqEntity.seqForWk2CD = arrForWk2;
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.wk2Seq reloadData];
}
Just to add on, the arrays are of data type NSMutableArray and I'm trying to store them into attributes of type "Transformable".
After researching around I managed to solve the problem. I'm not sure how it actually works but I solved it by rearranging the code when I'm trying to fetch the data.
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"Sequence" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = descriptor;
NSError *error;
NSArray *fetchObj = [context executeFetchRequest:request error:&error];
if(fetchObj == nil) {
NSLog(#"Error occured when trying to fetch.");
}
else {
if(fetchObj.count == 0) {
NSLog(#"No objects saved");
else {
NSManagedObject *sequence = (NSManagedObject *)[fetchObj objectAtIndex:0];
NSLog(#"1 - %#", sequence);
arrForWk1 = [sequence valueForKey:#"seqForWk1CD"];
NSLog(#"%#", arrForWk1);
arrForWk2 = [sequence valueForKey:#"seqForWk2CD"];
NSLog(#"%#", arrForWk2);
NSLog(#"2 - %#", sequence);
}
I've tried also tried 2 ways of saving the data. In the if statement where I tried to save the data, I converted the NSMutableArrays to NSArrays.
else if(check == 2) {
test2 = storeArray;
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Sequence" inManagedObjectContext:context];
NSArray *tArr = [arrForWk1 copy];
NSArray *tArr2 = [arrForWk2 copy];
seqEntity.seqForWk1CD = tArr;
seqEntity.seqForWk2CD = tArr2;
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
The second way I tried is by using an answer from Core Data not saving changes to Transformable property
id temp = [seqEntity seqForWk1CD];
id temp2 = [seqEntity seqForWk2CD];
temp = arrForWk1;
temp2 = arrForWk2;
[seqEntity setSeqForWk1CD:temp];
[seqEntity setSeqForWk2CD:temp2];
Apparently it worked somehow.
A problem I'm facing right now is that when I try to save objects, Core Data appears to only save the first one.
Code to save objects
- (void)savingResCore {
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
ResultMO *resEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Result" inManagedObjectContext:context];
if(indCheck == 1) {
resEntity.result1 = pena1;
resEntity.resuName1 = penaNam1;
}
if(indCheck == 2) {
resEntity.result2 = pena2;
resEntity.resuName2 = penaNam2;
}
if(indCheck == 3) {
resEntity.result3 = pena3;
resEntity.resuName3 = penaNam3;
}
_seqNameChk = [NSNumber numberWithInt:[_seqNameChk intValue] - 1];
if([_seqNameChk isEqual:#(0)]){
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
}
Code to fetch objects
- (void)fetchResCore {
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"Result" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = descriptor;
NSError *error;
NSArray *contArr = [context executeFetchRequest:request error:&error];
if(contArr == nil){
NSLog(#"Problems fetching data.");
}
else if(contArr != nil) {
if(contArr.count == 0) {
NSLog(#"No objects saved");
}
else {
NSManagedObject *resu = (NSManagedObject *)[contArr objectAtIndex:0];
NSLog(#"1 - %#", resu);
pena1 = [resu valueForKey:#"result1"];
penaNam1 = [resu valueForKey:#"resuName1"];
NSLog(#"%#", pena1);
pena2 = [resu valueForKey:#"result2"];
penaNam2 = [resu valueForKey:#"resuName2"];
NSLog(#"%#", pena2);
pena3 = [resu valueForKey:#"result3"];
penaNam3 = [resu valueForKey:#"resuName3"];
NSLog(#"%#", pena3);
NSLog(#"2 - %#", resu);
}
}
}
For example, I have a button that increments indCheck by one each time it is pressed and each time it calls up the fetchResCore method. Assume seqNameChk = 2. For the first time, the attributes result1 & resuName1 gets saved successfully. After the button is pressed consecutively, it no longer saves data. Hence, attributes result2, resuName2, result3 & resuName3 does not contain data when retrieved at the fetchResCore method.
Result printed in the console for resu
resuName1 = "(\n Alan\n)";
resuName2 = nil;
resuName3 = nil;
result1 = "(\n 100\n)";
result2 = nil;
result3 = nil;
Result printed in the console for contArr
result1 = \"(\\n 100\\n)\";\n result2 = nil;\n result3 = nil;\n
resuName1 = \"(\\n Alan\\n)\";\n resuName2 = nil;\n resuName3 = nil;\n
As Jon Rose had pointed out,
I would assume that they are all in the array contArr but you are only looking at the first one: NSManagedObject *resu = (NSManagedObject *)[contArr objectAtIndex:0];
I printed out contArr.count and it resulted in a 3. I've also printed out the contents of the array by using a for loop and again, 3 elements which were populated, was displayed.
To solve the problem, I had to rectify [contArr objectAtIndex:0] as it only displayed the first element in the array.
In the fetchResCore method
for(int i = 0; i < contArr.count; i++){
NSManagedObject *resu = (NSManagedObject *)[contArr objectAtIndex:i];
NSLog(#"1 - %#", resu);
if(pena1.count == 0 && penaNam1.count == 0){
pena1 = [resu valueForKey:#"result1"];
penaNam1 = [resu valueForKey:#"resuName1"];
NSLog(#"%#", pena1);
}
if(pena2.count == 0 && penaNam2.count == 0){
pena2 = [resu valueForKey:#"result2"];
penaNam2 = [resu valueForKey:#"resuName2"];
NSLog(#"%#", pena2);
}
if(pena3.count == 0 && penaNam3.count == 0){
pena3 = [resu valueForKey:#"result3"];
penaNam3 = [resu valueForKey:#"resuName3"];
NSLog(#"%#", pena3);
}
}
Thanks again, Jon Rose for pointing out a simple mistake that took me days to figure out.
I'm trying to get to select or button one or two button the application will create an entity related to that neighborhood and thus show the following screens according to the selection. To avoid the creation of entities each time decided to use the NSUserDefaults and implementation is this:
- (void)carregarEntidadeLojaComId:(NSString *)identificadorLoja keyNSUserDefault:(NSString *)key {
if (![[NSUserDefaults standardUserDefaults] objectForKey:key]) {
NSManagedObjectContext *contexto = [self managedObjectContext];
NSArray *arrayLojas = [Utils carregarArrayPlist:identificadorLoja];
NSArray *atributosComuns = #[#"titulo", #"subtitulo", #"telefone", #"endereco"];
for (NSDictionary *dicionario in arrayLojas) {
loja = [NSEntityDescription insertNewObjectForEntityForName:#"Loja" inManagedObjectContext:contexto];
categoria = [NSEntityDescription insertNewObjectForEntityForName:#"Categoria" inManagedObjectContext:contexto];
quadra = [NSEntityDescription insertNewObjectForEntityForName:#"Quadra" inManagedObjectContext:contexto];
//Loop para atributos comuns
for (NSString *atributo in atributosComuns) {
[loja setValue:[dicionario objectForKey:atributo] forKey:atributo];
}
[categoria setValue:[dicionario objectForKey:#"categoria"] forKey:#"nome"];
[loja setValue:categoria forKey:#"categoria"];
[quadra setValue:[dicionario objectForKey:#"quadra"] forKey:#"nome"];
[loja setValue:quadra forKey:#"quadra"];
}
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
Turning the first time, the application identifies that there is no key and enters the if statement, therefore, create the entity. Already from the second time you use the application it will not enter the if statement, but he can not use the entity that has been created and so the screens that depend on them are not fulfilled. My question is how to force the application to use the entity that has been created the first time that the user clicked the application.
Is there really NSUserDefaults needed?
Why not just query entity from CoreData itself, like this:
- (void)carregarEntidadeLojaComId:(NSString *)identificadorLoja keyNSUserDefault:(NSString *)key {
NSManagedObjectContext *contexto = [self managedObjectContext];
NSFetchRequest *request = [NSFetchRequest new];
request.entity = [NSEntityDescription entityForName:#"Loja"
inManagedObjectContext:contexto];
request.predicate = [NSPredicate predicateWithFormat:#"identificador = %#", identificadorLoja];
NSError *error = nil;
NSArray *fetchedObjects = [contexto executeFetchRequest:request error:&error];
NSArray *lojas = nil;
if ((error == nil) && [fetchedObjects count] > 0)
lojas = fetchedObjects;
if (!lojas) {
NSArray *arrayLojas = [Utils carregarArrayPlist:identificadorLoja];
NSArray *atributosComuns = #[#"titulo", #"subtitulo", #"telefone", #"endereco"];
for (NSDictionary *dicionario in arrayLojas) {
loja = [NSEntityDescription insertNewObjectForEntityForName:#"Loja" inManagedObjectContext:contexto];
//Loop para atributos comuns
for (NSString *atributo in atributosComuns) {
[loja setValue:[dicionario objectForKey:atributo] forKey:atributo];
}
[loja setValue:[self categoriaWithNome:[dicionario objectForKey:#"categoria"]
inContexto:contexto]
forKey:#"categoria"];
[loja setValue:[self quadraWithNome:[dicionario objectForKey:#"quadra"]
inContexto:contexto]
forKey:#"quadra"];
[loja setValue:identificadorLoja
forKey:#"identificador"];
}
} else {
// do what you want with loja's, previously stored in CoreData
for (NSManagedObject *entity in lojas)
...
}
}
- (NSManagedObject *) categoriaWithNome:(NSObject *)nome inContexto:(NSManagedObjectContext *)contexto {
NSManagedObject *categoria = [NSEntityDescription insertNewObjectForEntityForName:#"Categoria" inManagedObjectContext:contexto];
[categoria setValue:nome forKey:#"nome"];
return categoria;
}
- (NSManagedObject *) quadraWithNome:(NSObject *)nome inContexto:(NSManagedObjectContext *)contexto {
NSManagedObject *quadra = [NSEntityDescription insertNewObjectForEntityForName:#"Quadra" inManagedObjectContext:contexto];
[quadra setValue:nome forKey:#"nome"];
return quadra;
}
Upd.
Added predicate to request and removed limit, as it seems like you have multiple loja's for each identificadorLoja.
I am facing problems when i tries to save 40,000 records into CoreData Entity.
I am getting 40,000 records by consuming the webservice using AFNetworking, the response is in JSON. Than i divide the data into 4 , 10000 record chunks and then assign these 4 chunks to separate NSOperation objects (i have created subclass of NSOperation) and add these NSOperation Objects to NSOperationQueue.
The problem is that this way it is taking too much time to save the data into CoreData. And i want to find a solution where i can load the data very quickly.
This is the code in which i am creating NSOperation objects and adding them to NSOperationQueue.
- (void)casesResponseReceived:(NSArray*)array
{
id responseObject = [array objectAtIndex:0];
NSManagedObjectContext *moc = [array objectAtIndex:1];
NSString *responseString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSArray *response = [responseString JSONValue];
NSString *responseStr = [response JSONRepresentation];
NSRange range = [responseStr rangeOfString:#"["];
int index = 0;
int objectsCount = 5000;
if (range.location == 0) {
NSInteger count = objectsCount;
totalOperationsCount = 0;
completedOperationsCount = 0;
self.myQueue = [[NSOperationQueue alloc] init];
while (count == objectsCount) {
if ((index+count) > [response count]) {
count = [response count] - index;
}
NSArray *subArray = [response subarrayWithRange:NSMakeRange(index, count)];
index += objectsCount;
CaseParseOperation *operation = [[CaseParseOperation alloc] initWithData:subArray MOC:moc];
operation.delegate = self;
totalOperationsCount++;
[self.myQueue addOperation:operation];
}
/*
if (self.delegate && [self.delegate respondsToSelector:#selector(serviceHelperDidCasesReceivedSuccessful:)]) {
[self.delegate serviceHelperDidCasesReceivedSuccessful:self];
}*/
}
else {
if (self.delegate && [self.delegate respondsToSelector:#selector(serviceHelperDidCasesReceivedFailed:)]) {
[self.delegate serviceHelperDidCasesReceivedFailed:self];
}
}}
CaseOperation.h
#class CaseParseOperation;
#protocol CaseParseOperationProtocol <NSObject>
-(void)caseParseOperationDidOperationComplete: (CaseParseOperation*)caseParseOperation;
#end
#interface CaseParseOperation : NSOperation
#property (nonatomic, weak) id<CaseParseOperationProtocol> delegate;
-(id)initWithData:(NSArray*)parseData MOC:(NSManagedObjectContext*)moc;
#end
CaseOperation.m
#interface CaseParseOperation()
#property (nonatomic, copy) NSArray *casesData;
#property (nonatomic, strong) NSManagedObjectContext *mainMOC;
#property (nonatomic, strong) NSManagedObjectContext *localMOC;
#end
#implementation CaseParseOperation
- (id)initWithData:(NSArray*)parseData MOC:(NSManagedObjectContext*)moc
{
self = [super init];
if (self) {
self.casesData = [parseData copy];
self.mainMOC = moc;
}
return self;
}
- (void)main
{
#autoreleasepool {
self.localMOC = [[NSManagedObjectContext alloc] init];
self.localMOC.persistentStoreCoordinator = self.mainMOC.persistentStoreCoordinator;
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(mergeChanges:)
name: NSManagedObjectContextDidSaveNotification
object: self.localMOC];
[self parseData];
}
}
-(void) mergeChanges: (NSNotification*) saveNotification {
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainMOC mergeChangesFromContextDidSaveNotification:saveNotification];
});
if (self.delegate && [self.delegate respondsToSelector:#selector(caseParseOperationDidOperationComplete:)]) {
[self.delegate caseParseOperationDidOperationComplete:self];
}
}
- (void)parseData
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Case" inManagedObjectContext:self.localMOC];
fetchRequest.entity = ent;
NSString *predicateString = [NSString stringWithFormat:#"caseNumber == $caseNumber"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
//NSMutableArray *insertedObjects = [[NSMutableArray alloc] init];
for (NSMutableDictionary *dic in self.casesData) {
if (self.isCancelled) {
break;
}
NSString *desc = [dic valueForKey:#"description"];
BOOL enabled = [[dic valueForKey:#"enabled"] boolValue];
NSString *billToCustomerNo = [dic valueForKey:#"billToCustomerNo"];
NSString *caseNo = [dic valueForKey:#"caseNo"];
NSString *billToName = [dic valueForKey:#"billToName"];
NSString *personResponsible = [dic valueForKey:#"personResponsible"];
NSDictionary *variables = #{ #"caseNumber" : caseNo };
fetchRequest.predicate = [predicate predicateWithSubstitutionVariables:variables];
NSArray *matchedObj = [self.localMOC executeFetchRequest:fetchRequest error:nil];
if ([matchedObj count] > 0) {
Case *caseObj = [matchedObj objectAtIndex:0];
caseObj.isEnabled = [NSNumber numberWithBool:enabled];
caseObj.caseDescription = desc;
caseObj.customerNumber = billToCustomerNo;
caseObj.customerName = billToName;
caseObj.personResponsible = personResponsible;
}
else {
/*
Case *caseObj = [[Case alloc] initWithEntity:[NSEntityDescription entityForName:#"Case"
inManagedObjectContext:self.localMOC] insertIntoManagedObjectContext:nil];
caseObj.caseNumber = caseNo;
caseObj.customerName = billToName;
caseObj.customerAddress = #"";
caseObj.customerPhone = #"";
caseObj.caseDescription = desc;
caseObj.customerNumber = billToCustomerNo;
caseObj.isEnabled = [NSNumber numberWithBool:enabled];
caseObj.personResponsible = personResponsible;
[insertedObjects addObject:caseObj];
*/
[Case createObjectWithCaseNumber:caseNo customerName:billToName customerAddress:#"" customerPhone:#"" caseDescription:desc customerNumber:billToCustomerNo isEnabled:enabled personResponsible:personResponsible MOC:self.localMOC];
}
}
/*
if ([insertedObjects count] > 0) {
NSError *error = nil;
BOOL isInserted = [self.localMOC obtainPermanentIDsForObjects:insertedObjects error:&error];
if (error || !isInserted) {
NSLog(#"Error occured");
}
}
*/
if ([self.localMOC hasChanges]) {
[self.localMOC save:nil];
}
}
#end
The first thing to do is run Instruments and find the bottlenecks, as #jrturton recommends.
But there's one huge glaring bottleneck that's apparent from reading the code. To avoid duplicates, you're doing a fetch-- for every incoming instance. With 40k records you'll have to do 40k fetches during the import process, and that's going to be slow no matter what.
You can improve that by processing the data in batches:
Get a bunch of caseNumber values into an array
Do a fetch with a predicate of caseNumber IN %#, with the array as the argument.
Use that array to check for duplicates.
You'll need to experiment a little to see how many "a bunch" is in step 1. Higher numbers mean fewer fetches, which is good for speed. But higher numbers also mean more memory use.
For a more detailed discussion, see Apple's Efficiently Importing Data guide, especially the section named "Implementing Find-or-Create Efficiently".
Thanks guys valuable suggestions. But i have solved that issue by just altering some technique in the parseData function.
-(void)parseData
{
NSString *predicateString = [NSString stringWithFormat:#"caseNumber == $caseNumber"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
NSArray *allCases = [Case getAllCaseObjectsWithMOC:self.localMOC];
for (NSMutableDictionary *dic in self.casesData) {
if (self.isCancelled) {
break;
}
NSString *caseNo = [dic valueForKey:#"caseNo"];
NSDictionary *variables = #{ #"caseNumber" : caseNo };
predicate = [predicate predicateWithSubstitutionVariables:variables];
NSArray *matchedObj = [allCases filteredArrayUsingPredicate:predicate];
if ([matchedObj count] == 0) {
NSString *desc = [dic valueForKey:#"description"];
BOOL enabled = [[dic valueForKey:#"enabled"] boolValue];
NSString *billToCustomerNo = [dic valueForKey:#"billToCustomerNo"];
NSString *billToName = [dic valueForKey:#"billToName"];
NSString *personResponsible = [dic valueForKey:#"personResponsible"];
[Case createObjectWithCaseNumber:caseNo customerName:billToName customerAddress:#"" customerPhone:#"" caseDescription:desc customerNumber:billToCustomerNo isEnabled:enabled personResponsible:personResponsible MOC:self.localMOC];
}
}
if ([self.localMOC hasChanges]) {
[self.localMOC save:nil];
}
}
After I tried to ask twice and explain myself, I did a dig and I think I can now explain my problem better:
1) I'm using core data to save to NSManagedObjects: CoreDataTrap & CoreDataAllTraps.
First time, I'm parsing a large xml and then convert to array and then add the details to CoreDataAllTraps, this operation going well as I know because I did a log.
2) Then, Just for the test, I'm fetching all of the records and log the total number of them.
Those functions give me the correct number of records.
3) Then just initializing a few variables.
4) Then initializing my quad tree.
Which gives me back my assertion error.
Error: fetchedObjects have no records.
5) Then, all the rest of the functions that using core data gives me error back of course, because there is no data.
Relevant (numbered) code:
1:
---
- (void)addOrUpdateTrap:(Traps*)trapObject
{
NSManagedObjectContext *context = generateManagedObjectContext();
int trapID = trapObject.getTrapID;
CoreDataAllTraps *trapEntity = nil;
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:kCORE_DATA_ALL_TRAPS_ENTITY];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"trapID == %d", trapID];
[fetchRequest setPredicate:predicate];
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
if (results == nil)
{
// Handle error
}
else if (results.count == 0)
{
// Nothing to update, add new trap
// Create a new record (row)
trapEntity = [NSEntityDescription insertNewObjectForEntityForName:kCORE_DATA_ALL_TRAPS_ENTITY inManagedObjectContext:context];
}
else
{
trapEntity = results[0]; // There should be only one object for the ID.
}
if (trapEntity != nil)
{
// Set properties for new or existing object ...
// Int
[trapEntity setTrapID:[NSNumber numberWithInt:trapObject.getTrapID]];
[trapEntity setType:[NSNumber numberWithInt:trapObject.getTrapType]];
[trapEntity setDist:[NSNumber numberWithInt:trapObject.getTrapDistanceToCar]];
[trapEntity setDist_to_close_point:[NSNumber numberWithInt:trapObject.getTrapDistanceToClosePoint]];
[trapEntity setActive:[NSNumber numberWithInt:trapObject.isActive]];
[trapEntity setAlert:[NSNumber numberWithInt:trapObject.isAlert]];
[trapEntity setAlarmDistance:[NSNumber numberWithInt:trapObject.alarmDistance]];
[trapEntity setRoadNumber:[NSNumber numberWithInt:trapObject.roadNumber]];
[trapEntity setPolys:[NSNumber numberWithInt:trapObject.polygons]];
[trapEntity setEnter_to_area:[NSNumber numberWithInt:trapObject.getTrapEnterToArea]];
// Double
[trapEntity setLat:[NSNumber numberWithDouble:trapObject.getTrapLat]];
[trapEntity setLon:[NSNumber numberWithDouble:trapObject.getTrapLon]];
[trapEntity setClose_point_lat:[NSNumber numberWithDouble:trapObject.getTrapClosePointLat]];
[trapEntity setClose_point_lon:[NSNumber numberWithDouble:trapObject.getTrapClosePointLon]];
// NSString
[trapEntity setLastTrapAlarm:[NSString stringWithFormat:#"%li", trapObject.getTrapLastAlarm]];
[trapEntity setPoly0:trapObject.getTrapPolygonA];
[trapEntity setPoly1: trapObject.getTrapPolygonB];
[trapEntity setPoly2: trapObject.getTrapPolygonC];
[trapEntity setPolygonAzimut1: trapObject.getTrapPolygonAzimuthA];
[trapEntity setPolygonAzimut2: trapObject.getTrapPolygonAzimuthB];
[trapEntity setPolygonAzimut3: trapObject.getTrapPolygonAzimuthC];
[trapEntity setDesc: trapObject.getTrapDesc];
// etc. for all properties ...
error = nil;
if ([context save:&error] == NO) {
NSLog(#"%s error saving: %#\n%#", __PRETTY_FUNCTION__, error.localizedDescription, error.userInfo);
}
else {
[context reset];
}
}
}
2:
---
- (void)saveArray:(NSArray*)array
{
kNETROADS_CONTEXT.arrayOfAllTraps = self.arrayOfAllTraps = array.mutableCopy;
NSLog(#"Total number of traps: %d", self.arrayOfAllTraps.count);
NSManagedObjectContext *context = generateManagedObjectContext();
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:kCORE_DATA_ALL_TRAPS_ENTITY inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
NSLog(#"TrapService - fetchedObjects.count: %d", fetchedObjects.count);
if (fetchedObjects == nil || fetchedObjects.count == 0) {
NSLog(#"saveArray - localizedDescription: %#, userInfo: %#", error.localizedDescription, error.userInfo);
}
[self readArray];
}
- (void)readArray
{
NSManagedObjectContext *context = generateManagedObjectContext();
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:kCORE_DATA_ALL_TRAPS_ENTITY inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
NSLog(#"readArray - fetchedObjects.count: %d", fetchedObjects.count);
if (fetchedObjects == nil || fetchedObjects.count == 0) {
NSLog(#"readArray - localizedDescription: %#, userInfo: %#", error.localizedDescription, error.userInfo);
}
}
3:
---
- (void)initVariables
{
db = [[DataBase alloc] init];
//dbat = [[DataBaseAllTraps alloc] init];
dbat = [DataBaseAllTraps getInstance];
kRECEIVER_CONTEXT.db = [[DataBase alloc] init];
[db deleteTrapsTable];
[dbat deleteTrapsTable];
self.dictAddUserLocations = [[NSMutableDictionary alloc] init];
self.arrayOfAllTraps = [Netroads sharedInstance].arrayOfAllTraps;
self.arrayOfLocations = [[NSMutableArray alloc] init];
self.firstOnLocationChanged = YES;
self.mLocation = [CLLocation new];
self.mLastLocation = [CLLocation new];
self.globalLocation = [CLLocation new];
self.lastGlobalLocation = [CLLocation new];
self.myLocations = [[NSMutableArray alloc] init];
self.accuracy = #"N/A";
self.closeTrap = [[Traps alloc] init];
self.notification = [NSNotificationCenter defaultCenter];
// [self.notification addObserver:self selector:#selector(onReceive:) name:kSend_To_Receiver_Notification object:nil];
}
4:
---
- (void)initializeQuadTree
{
self.qTree = [[QuadTree alloc] init];
BOOL success = YES;
NSManagedObjectContext *context = generateManagedObjectContext();
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:kCORE_DATA_ALL_TRAPS_ENTITY inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil || fetchedObjects.count == 0)
{
NSLog(#"initializeQuadTree - localizedDescription: %#, userInfo: %#", error.localizedDescription, error.userInfo);
success = NO;
}
NSLog(#"initializeQuadTree - fetchedObjects.count: %d", fetchedObjects.count);
NSAssert(fetchedObjects != nil, #"Error: fetchedObjects = nil");
NSAssert(fetchedObjects.count > 0, #"Error: fetchedObjects have no records.");
if (success)
{
for (CoreDataAllTraps *trap in fetchedObjects)
{
double latitude = trap.lat.doubleValue;
double longitude = trap.lon.doubleValue;
double closePointLat = trap.close_point_lat.doubleValue;
double closePointLon = trap.close_point_lon.doubleValue;
DummyAnnotation *trapAnnotation = [[DummyAnnotation alloc] init];
if (closePointLat != 0.0 || closePointLon != 0.0) trapAnnotation.coordinate = CLLocationCoordinate2DMake(closePointLat, closePointLon);
else trapAnnotation.coordinate = CLLocationCoordinate2DMake(latitude, longitude);
[self.qTree insertObject:trapAnnotation];
}
}
else
{
for (Traps *trap in kNETROADS_CONTEXT.arrayOfAllTraps)
{
double latitude = trap.lat;
double longitude = trap.lon;
double closePointLat = trap.closePointLat;
double closePointLon = trap.closePointLon;
DummyAnnotation *trapAnnotation = [[DummyAnnotation alloc] init];
if (closePointLat != 0.0 || closePointLon != 0.0) trapAnnotation.coordinate = CLLocationCoordinate2DMake(closePointLat, closePointLon);
else trapAnnotation.coordinate = CLLocationCoordinate2DMake(latitude, longitude);
[self.qTree insertObject:trapAnnotation];
}
}
NSLog(#"TOTAL NUMBER OF TRAPS (%s): %i", __PRETTY_FUNCTION__, success?fetchedObjects.count:[Netroads sharedInstance].arrayOfAllTraps.count);
}
Side notes:
* After calling initializeQuadTree i'm initializing the location manager.
* In location manager I've a dispatch_async that wrap the whole code inside.
* After I done with all initializing, the main of the code happens via location manager didUpdateLocations.
* For each and every using in core data I'm generating new NSManagedObjectConext like this:
FOUNDATION_EXPORT NSManagedObjectContext *generateManagedObjectContext()
{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator;
return context;
}
[db deleteTrapsTable];
[dbat deleteTrapsTable];
The table deleted after you create it