data needs 10 min to load data into core-database - objective-c

I am working with a core database to save my data that I get back from a webservice into my app. But this is very very slow. Any idea how this comes? What I do is the following. I have a class for getting the data from the webservice. like you can see over here.
GenkData.h
+ (NSArray *)getNews;
GenkData.m
+ (NSDictionary *)executeGenkFetch:(NSString *)query
{
query = [NSString stringWithFormat:#"%#", query];
query = [query stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// NSLog(#"[%# %#] sent %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), query);
NSData *jsonData = [[NSString stringWithContentsOfURL:[NSURL URLWithString:query] encoding:NSUTF8StringEncoding error:nil] dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *results = jsonData ? [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves error:&error] : nil;
if (error) NSLog(#"[%# %#] JSON error: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), error.localizedDescription);
// NSLog(#"[%# %#] received %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), results);
return results;
}
+ (NSArray *)getNews
{
NSString *request = [NSString stringWithFormat:#"http://www.krcgenk.be/mobile/json/request/news/type/ipad"];
return [[self executeGenkFetch:request] valueForKey:#"news"];
}
Then in my first view controller that shows up I have a method that for fetching my data in my core database.
- (void)fetchGenkDataIntoDocument:(UIManagedDocument *)document
{
NSLog(#"Fetch data");
dispatch_queue_t fetchQ = dispatch_queue_create("Genk fetcher", NULL);
dispatch_async(fetchQ, ^{
NSArray *news = [GenkData getNews];
[document.managedObjectContext performBlock:^{ // perform in the NSMOC's safe thread (main thread)
int newsId = 0;
//int trainingIndex = -1;
for (NSDictionary *genkInfo in news) {
NSLog(#"Begint met nieuwsitem");
newsId++;
[News newsWithGenkInfo:genkInfo inManagedObjectContext:document.managedObjectContext withNewsId:newsId];
NSLog(#"Einde met nieuwsitem");
}
}];
});
NSLog(#"einde fethdata");
}
Next I have a category of the NSManagerd object subclass of news where I do the following.
+ (News *)newsWithGenkInfo:(NSDictionary *)genkInfo
inManagedObjectContext:(NSManagedObjectContext *)context
withNewsId:(int)newsId
{
News *news = nil;
news = [NSEntityDescription insertNewObjectForEntityForName:#"News"
inManagedObjectContext:context];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"dd/MM/yyyy"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT+0:00"]];
NSDate *date = [dateFormatter dateFromString:[genkInfo objectForKey:NEWS_DATE]];
news.title = [genkInfo objectForKey:NEWS_TITLE];
news.date = date;
news.genk_description = [genkInfo objectForKey:NEWS_DESCRIPTION];
news.imgurl = [genkInfo objectForKey:NEWS_IMGURL];
news.shortdescription = [genkInfo objectForKey:NEWS_SHORTDESCRIPTION];
news.url = [genkInfo objectForKey:NEWS_URL];
news.imagecopyright = [genkInfo objectForKey:NEWS_IMAGECOPYRIGHT];
news.news_id = [NSNumber numberWithInt:newsId];
return news;
}
I am doing the following proces like 10 times for 10 different entities. All of those attributes are mostly strings. Still this takes up to 10 minutes to load in all the data. Can somebody help me ?
Kind regards.

Dima's answer of checking with the profiler helped me with this question and solved my problem

Related

How do I avoid app termination due to memory issue?

I am fetching 700.000 rows from a web service and my app is crashing after a while being terminated due to memory issue, it consumes about 1 GB of ram before it crashes, the code is fairly simple, I fetch the JSON, I put into an array, I loop the array and insert into core data and once done I save the context
the code is shown below
+ (void)fetchTillDataAll:(int)tillId :(int)startAtRow :(int)takeNoOfRows {
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchTillDataAll:tillId = %d, startAtRow = %d, takeNoOfRows = %d", tillId, startAtRow, takeNoOfRows);
}
NSString *finalURL = [NSString stringWithFormat:#"https://host.domain.com:5443/api/till/tilldatav2/%d?StartAtRow=%d&TakeNoOfRows=%d",tillId, startAtRow, takeNoOfRows];
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:finalURL]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchTillDataAll:Transport error %#", error);
}
} else {
NSHTTPURLResponse *responseHTTP;
responseHTTP = (NSHTTPURLResponse *) response;
if(responseHTTP.statusCode != 200) {
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchTillDataAll:Server Error %d", (int) responseHTTP.statusCode);
}
} else {
NSArray *tillBasicDataArray = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchTillDataAll:tillBasicDataArray count = %lu", (unsigned long)[tillBasicDataArray count]);
NSLog(#"WebServices:fetchTillDataAll:tillBasicDataArray looks like %#",tillBasicDataArray);
}
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
//NSManagedObjectContext *context =
//appDelegate.persistentContainer.viewContext;
//context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
NSPersistentContainer *container = appDelegate.persistentContainer;
[container performBackgroundTask:^(NSManagedObjectContext *context ) {
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
NSDictionary *tillBasicDataDict = Nil;
//Loop through the array and for each dictionary insert into local DB
// lets work on concurrency here
for (id element in tillBasicDataArray){
tillBasicDataDict = element;
NSString *itemId = [tillBasicDataDict objectForKey:#"itemId"];
NSString *brandId = [tillBasicDataDict objectForKey:#"companyId"];
NSString *languageId = [tillBasicDataDict objectForKey:#"languageCode"];
NSString *colorCode = [NSString stringWithFormat:#"%#", [tillBasicDataDict objectForKey:#"colorCode"]];
NSString *discountable = [tillBasicDataDict objectForKey:#"discountable"];
NSString *exchangeable = [tillBasicDataDict objectForKey:#"exchangeable"];
NSString *noos14 = [tillBasicDataDict objectForKey:#"noos14"];
NSString *sizeCode = [NSString stringWithFormat:#"%#", [tillBasicDataDict objectForKey:#"sizeCode"]];
NSString *taxGroup = [tillBasicDataDict objectForKey:#"taxGroupId"];
NSString *taxRegion = [tillBasicDataDict objectForKey:#"taxRegion"];
NSString *tradeItemDesc = [tillBasicDataDict objectForKey:#"tradeItemDesc"];
NSString *withTax = [tillBasicDataDict objectForKey:#"withTax"];
NSString *status = [tillBasicDataDict objectForKey:#"status"];
// Use Core Data FMD
NSManagedObject *newPimItem = Nil;
newPimItem = [NSEntityDescription
insertNewObjectForEntityForName:#"TillData"
inManagedObjectContext:context];
[newPimItem setValue:itemId forKey:#"itemId"];
[newPimItem setValue:brandId forKey:#"brandId"];
[newPimItem setValue:languageId forKey:#"languageCode"];
[newPimItem setValue:colorCode forKey:#"colorCode"];
[newPimItem setValue:discountable forKey:#"discountable"];
[newPimItem setValue:exchangeable forKey:#"exchangeable"];
[newPimItem setValue:noos14 forKey:#"noos14"];
[newPimItem setValue:sizeCode forKey:#"sizeCode"];
[newPimItem setValue:[NSNumber numberWithInt:[taxGroup intValue]] forKey:#"taxGroup"];
[newPimItem setValue:taxRegion forKey:#"taxRegion"];
[newPimItem setValue:tradeItemDesc forKey:#"tradeItemDesc"];
[newPimItem setValue:[NSNumber numberWithInt:[withTax intValue]] forKey:#"withTax"];
[newPimItem setValue:[NSNumber numberWithInt:[status intValue]] forKey:#"status"];
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchTillDataAll:ItemId in loop = %#", itemId);
NSLog(#"WebServices:fetchTillDataAll:newPimItem = %#", newPimItem);
NSLog(#"WebServices:fetchTillDataAll:CoreData error = %#", error);
}
}
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Failure to save context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
} else {
NSUserDefaults *tillUserDefaults = [NSUserDefaults standardUserDefaults];
[tillUserDefaults setInteger:1 forKey:#"hasTillData"];
[tillUserDefaults synchronize];
}
}];
}
}
}] resume];
}
What can I do minimize the foot print so that I am able to download the data? I absolutely must have the data locally in order to allow offline capabilities in the app
----- EDIT -----
After implementing splitting the NSArray into an array of array I still get the same problem, below if the new code as suggested:
split method
+ (NSArray *) splitIntoArraysOfBatchSize:(NSArray *)originalArray :(int)batchSize {
NSMutableArray *arrayOfArrays = [NSMutableArray array];
for(int j = 0; j < [originalArray count]; j += batchSize) {
NSArray *subarray = [originalArray subarrayWithRange:NSMakeRange(j, MIN(batchSize, [originalArray count] - j))];
[arrayOfArrays addObject:subarray];
}
return arrayOfArrays;
}
Looping through the array as follows
+(void)fetchPricelistAll:(int)pricelistId :(int)startAtRow :(int)takeNoOfRows;
{
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchPriceList:priceListId = %d", pricelistId);
}
NSString *finalURL = [NSString stringWithFormat:#"https://host.domain.com:5443/api/till/tillpricelistv2/%d?StartAtRow=%d&TakeNoOfRows=%d",pricelistId, startAtRow, takeNoOfRows];
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:finalURL]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchPriceList:Transport error %#", error);
}
} else {
NSHTTPURLResponse *responseHTTP;
responseHTTP = (NSHTTPURLResponse *) response;
if(responseHTTP.statusCode != 200) {
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchPriceList:Server Error %d", (int) responseHTTP.statusCode);
}
} else {
NSArray *priceListObjectArray = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchPriceList:count = %lu", (unsigned long)[priceListObjectArray count]);
NSLog(#"WebServices:fetchPriceList:PricelistObjectArray looks like %#",priceListObjectArray);
}
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
NSPersistentContainer *container = appDelegate.persistentContainer;
NSArray *arrayOfArrays = [NWTillHelper splitIntoArraysOfBatchSize:priceListObjectArray :1000];
for(NSArray *batch in arrayOfArrays) {
[container performBackgroundTask:^(NSManagedObjectContext *context ) {
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
NSDictionary *priceListObjectDict;
//Loop through the array and for each dictionary insert into local DB
for (id element in batch) {
priceListObjectDict = element;
NSString *currencyName = [priceListObjectDict objectForKey:#"currencyName"];
NSString *price = [priceListObjectDict objectForKey:#"price"];
NSString *priceIncTax = [priceListObjectDict objectForKey:#"priceIncTAX"];
NSString *validFrom = [priceListObjectDict objectForKey:#"validFromDate"];
NSString *validTo = [priceListObjectDict objectForKey:#"validToDate"];
NSString *itemId = [priceListObjectDict objectForKey:#"itemID"];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"YYYY-MM-dd'T'HH:mm:ss"];
NSDate *validToDate = [dateFormat dateFromString:validTo];
NSDate *validFromDate = [dateFormat dateFromString:validFrom];
if([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchPriceList:validToDate: >>>> %# <<<<", validToDate);
NSLog(#"WebServices:fetchPriceList:validFromDate: >>>> %# <<<<", validFromDate);
}
if([NWTillHelper isDebug] == 1) {
NSLog(#"PimItemListView:tableView:context = %#", context);
}
NSManagedObject *newPrlItem = Nil;
newPrlItem = [NSEntityDescription
insertNewObjectForEntityForName:#"PriceList"
inManagedObjectContext:context];
[newPrlItem setValue:itemId forKey:#"itemId"];
[newPrlItem setValue:validToDate forKey:#"validTo"];
[newPrlItem setValue:validFromDate forKey:#"validFrom"];
[newPrlItem setValue:price forKey:#"price"];
[newPrlItem setValue:priceIncTax forKey:#"priceIncTax"];
[newPrlItem setValue:currencyName forKey:#"currencyName"];
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:fetchTillData:ItemId in loop = %#", itemId);
NSLog(#"WebServices:fetchTillData:newPrlItem = %#", newPrlItem);
NSLog(#"WebServices:fetchTillData:CoreData error = %#", error);
}
}
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Failure to save context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
} else {
NSUserDefaults *tillUserDefaults = [NSUserDefaults standardUserDefaults];
[tillUserDefaults setInteger:1 forKey:#"hasPriceList"];
[tillUserDefaults synchronize];
}
}];
}
}
}
}] resume];
}
It still raises to 2 GB and gets terminated, when the NSURLSession completion block hits memory usage is about 250, I assume that is after it downloads the entire data set into the NSArray, but after that when I want to write to core data all goes terribly wrong and it goes to 2 GB and gets terminated
Why is that?
First split up your large array into an array of arrays. Experiment with a good batch size that is not too large (that the app will crash) or too small (that will take a long time). I would suggest starting with 500. See here how do do this: What is an easy way to break an NSArray with 4000+ objects in it into multiple arrays with 30 objects each?. I assume you can turn that code into an array extension..
NSArray *arrayOfArrays = [tillBasicDataArray splitIntoArrayBatchSize:500];
Next you can enqueue many block that will each process only one of the many array and save it at the end.
for(NSArray *batch in arrayOfArrays){
[container performBackgroundTask:^(NSManagedObjectContext *context ) {
...
for (id element in batch){
...
each performBackgroundTask is enqueued into an internal operation and will process one at a time.
The rest of your code remains basically the same.

Recipes to update database in Core Data

I have a method for adding stamp to the card, from QR code reader to web service
my question is,
Here is my code:
- (void)addStamp
{
NSString *url = [_URL stringByAppendingString:#"add-stamp"];
NSString *post = [NSString stringWithFormat:#"userId=%#&code=%#", self.userId, self.code ];
post = [post stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *postData = [NSData dataWithBytes:[post UTF8String]
length:[post lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL
URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:600];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postData];
}
Rightnow I want to update my DB with new method -> if the code is repeated add stamp, if not create add a new card:
-(void)addStampInDB:(int)cardId
{
NSManagedObjectContext* managedObjectContext = [(AppDelegate*) [[UIApplication
sharedApplication] delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Card"
inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"remote_id = %d", cardId];
[fetchRequest setPredicate:predicate];
[managedObjectContext lock];
NSError *error;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest
error:&error];
[managedObjectContext unlock];
Card *d = nil;
if([fetchedObjects count] > 0 ){
d = [fetchedObjects objectAtIndex:0];
cardId++;
} else{
d = [NSEntityDescription insertNewObjectForEntityForName:#"Card"
inManagedObjectContext:managedObjectContext];
}
NSArray *test;
for(NSDictionary *stamp in test)
{
d.remote_id = [NSNumber numberWithInt:[[stamp objectForKey:#"id"] intValue]];
d.stampNumber = [NSNumber numberWithInt:[[stamp objectForKey:#"stampNumber"] intValue]];
d.createdAt = [NSDate dateWithTimeIntervalSince1970:[[stamp objectForKey:#"createdAt"]
intValue]];
[managedObjectContext lock];
NSError *error;
if (![managedObjectContext save:&error])
{
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
} else {
NSLog(#" %#", [error userInfo]);
}
}
[managedObjectContext unlock];
}
}
I don't know if I'm in a right way or not and also how I can test my method?
I'm not very sure about your question but I'll try to give some hints.
First
Card *d = nil;
if([fetchedObjects count] > 0 ){
d = [fetchedObjects objectAtIndex:0];
cardId++;
} else{
d = [NSEntityDescription insertNewObjectForEntityForName:#"Card"
inManagedObjectContext:managedObjectContext];
}
Here you have found an existing card, you use it, else you create a new. Question: what about cardId? Where is it used?
Second
for(NSDictionary *stamp in test)
{
d.remote_id = [NSNumber numberWithInt:[[stamp objectForKey:#"id"] intValue]];
d.stampNumber = [NSNumber numberWithInt:[[stamp objectForKey:#"stampNumber"] intValue]];
d.createdAt = [NSDate dateWithTimeIntervalSince1970:[[stamp objectForKey:#"createdAt"]
intValue]];
// other code here
}
If you loop test, you overwrite the value of your card d each time. Is it right?
Third
for(NSDictionary *stamp in test)
{
// other code here
}
// save here
Move the save out of the for loop. This boost performances in your app. It is not necessary to save each time in your loop. Do it at the end only.
Hope that helps.
P.S. If you want to know something else let me know.

Loading Core Data From Large JSON Causing App To Crash

I'm attempting to populate CoreData from a JSON file that consists of 170,000 plus dictionaries. The parsing of the json goes quick but when I start trying to add to CoreData I'm blocking the main thread for a long time and then the app eventually crashes. It crashes when calling the method [UIDocument saveToUrl:forSaveOperation:completionHandler] Here is my code. If anyone has an idea of what's causing it to crash or a more efficient way to load CoreData that would be greatly appreciated.
#property (nonatomic, strong) UIManagedDocument *wordDatabase;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.wordDatabase) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Word Database"];
self.wordDatabase = [[UIManagedDocument alloc] initWithFileURL:url];
}
}
- (void)setWordDatabase:(UIManagedDocument *)wordDatabase
{
if (_wordDatabase != wordDatabase) {
_wordDatabase = wordDatabase;
[self useDocument];
}
}
- (void)useDocument
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.wordDatabase.fileURL path]]) {
// does not exist on disk, so create it
[self.wordDatabase saveToURL:self.wordDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self setupFetchedResultsController];
[self prepopulateWordDatabaseWithDocument:self.wordDatabase];
}];
}
}
- (void)prepopulateWordDatabaseWithDocument:(UIManagedDocument *)document
{
dispatch_queue_t fetchQ = dispatch_queue_create("Word Fetcher", NULL);
dispatch_async(fetchQ, ^{
//Fetch the words from the json file
NSString *fileString = [[NSBundle mainBundle] pathForResource:#"words" ofType:#"json"];
NSString *jsonString = [[NSString alloc] initWithContentsOfFile:fileString encoding:NSUTF8StringEncoding error: NULL];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSArray *words = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
[document.managedObjectContext performBlock:^{
for (NSDictionary *dictionary in words)
{
[Word wordFromDictionary:dictionary inManagedObjectContext:document.managedObjectContext];
}
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
}];
});
dispatch_release(fetchQ);
}
What I ended up doing that stopped my app from crashing was allocating a new NSManagedObjectContext and peformed all my loading in the background. After saving I called my NSFetchedResultsController and the table repopulated.
- (void)prepopulateWordDatabaseWithDocument:(UIManagedDocument *)document
{
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.undoManager = nil;
backgroundContext.persistentStoreCoordinator = document.managedObjectContext.persistentStoreCoordinator;
[backgroundContext performBlock:^{
NSString *fileString = [[NSBundle mainBundle] pathForResource:#"words" ofType:#"json"];
NSString *jsonString = [[NSString alloc] initWithContentsOfFile:fileString encoding:NSUTF8StringEncoding error: NULL];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *parseError;
NSArray *words = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&parseError];
for (NSDictionary *dictionary in words)
{
[Word wordFromDictionary:dictionary inManagedObjectContext:backgroundContext];
}
NSError *loadError;
if ([backgroundContext save:&loadError]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setupFetchedResultsController];
});
}
}];
}

AFNetworking HTTPRequestOperation need to set array from completion block but this isn't working?

I'm using AFNetworking with AFHTTPRequestOperation to pull XML data from a webservice. This is working fine and im getting the data I need but I need to split this data into objects and initialize a NSMutableArray with this data. This is working in the completion block, but just before I return the array in my method the data is gone? How do I do this?
Here is some of my code:
NSMutableArray *result = [[NSMutableArray alloc] init];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString* response = [operation responseString];
NSData* xmlData = [response dataUsingEncoding:NSUTF8StringEncoding];
NSError *xmlError;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&xmlError];
NSArray *allElements = [doc.rootElement elementsForName:#"Misc"];
for (GDataXMLElement *current in allElements)
{
NSString *titel;
NSString *tekst;
NSArray *titels = [current elementsForName:#"Titel"];
if(titels.count > 0)
{
GDataXMLElement *firstTitel = (GDataXMLElement *) [titels objectAtIndex:0];
titel = firstTitel.stringValue;
} else continue;
NSArray *teksts = [current elementsForName:#"Tekst"];
if(teksts.count > 0)
{
GDataXMLElement *firstTekst = (GDataXMLElement *) [teksts objectAtIndex:0];
tekst = firstTekst.stringValue;
} else continue;
HVMGUniversalItem *item = [[HVMGUniversalItem alloc] initWithTitel:titel AndTekst:tekst];
[result addObject:item];
}
NSLog(#"%i", result.count);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [operation error]);
}];
[operation start];
NSLog(#"%i", result.count);
return result;
What am I doing wrong? Why isn't the data present in the array when returning?
Why isn't the data present in the array when returning?
Because AFNetworking use an async pattern. So the return code will be performed before the operation will be completed.
You need to use a different approach or follow Can AFNetworking return data synchronously (inside a block)?. The latter is discouraged.
A solution could be to:
-> Create a NSOperationQueue within your class that will include your operation. Create it as a property for your class like.
#property (nonatomic, strong, readonly) NSOperationQueue* downloadQueue;
- (NSOperationQueue*)downloadQueue
{
if(downloadQueue) return downloadQueue;
downloadQueue = // alloc init here
}
-> Create a property for your array (synthesize also it)
#property (nonatomic, strong) NSMutableArray* result;
-> Wrap your code within a specific method like doOperation.
self.result = [[NSMutableArray alloc] init];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
__weak YourClass* selfBlock = self;
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString* response = [operation responseString];
NSData* xmlData = [response dataUsingEncoding:NSUTF8StringEncoding];
NSError *xmlError;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&xmlError];
NSArray *allElements = [doc.rootElement elementsForName:#"Misc"];
for (GDataXMLElement *current in allElements)
{
NSString *titel;
NSString *tekst;
NSArray *titels = [current elementsForName:#"Titel"];
if(titels.count > 0)
{
GDataXMLElement *firstTitel = (GDataXMLElement *) [titels objectAtIndex:0];
titel = firstTitel.stringValue;
} else continue;
NSArray *teksts = [current elementsForName:#"Tekst"];
if(teksts.count > 0)
{
GDataXMLElement *firstTekst = (GDataXMLElement *) [teksts objectAtIndex:0];
tekst = firstTekst.stringValue;
} else continue;
HVMGUniversalItem *item = [[HVMGUniversalItem alloc] initWithTitel:titel AndTekst:tekst];
[selfBlock.result addObject:item];
}
NSLog(#"%i", result.count);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [operation error]);
}];
[downloadQueue addOperation:operation];
-> if you need to notify that result has object send a notification, use the delegate pattern, etc...
Hope that helps.

NSLog and Core Data

I have three Classes for which I am extending NSManagedObject (I know this isn't required but I wanted the property notation). Theses Classes are QuestionSet, Question and Answer. My data model is setup so that there's a many to many between them like so: QuestionSet <<->> Question <<->> Answer. I load up everything from a server and it save successfully meaning I look in the .db file and it's all how I expect, if I immediately NSLog the array questionsets they look great and if I go take a quiz it works great.
However if I NSLog that array anywhere but right after where I load them from the server, it crashes my app. The quiz still runs fine so I know the data is in there and in the expected format. Does this have something to do with Core Data clearing out space and then my relationships don't 'lazy loaded' when I'm just trying to log them? Sorry, still wrapping my head around core data.
This bit works, I load from the server and at the bottom I 'loadTrivia' and log what I get. The issue comes if at sometime later during the course of the app execution, an NSLog of the questionsets will fail.
- (void)loadQuestionSetFromNetworkWithID:(NSNumber *)p_id andTitle:(NSString *)p_title
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:kTriviaQuestionSetURL, [p_id intValue]]]];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSArray *questions = [[[sbjson objectWithString:json_string error:nil] valueForKeyPath:#"Q"] objectAtIndex:0];
NSError *error;
NSFetchRequest *questionsetRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *questionsetEntity = [NSEntityDescription entityForName:#"QuestionSet" inManagedObjectContext:[self managedObjectContext]];
NSPredicate *questionsetPredicate = [NSPredicate predicateWithFormat:#"id = %d", [p_id intValue]];
[questionsetRequest setEntity:questionsetEntity];
[questionsetRequest setPredicate:questionsetPredicate];
QuestionSet *qs = nil;
NSArray *questionSetObjects = [[self managedObjectContext] executeFetchRequest:questionsetRequest error:&error];
if (questionSetObjects == nil){
// Handle errors
}
if ([questionSetObjects count] > 0)
qs = [questionSetObjects objectAtIndex:0];
else
qs = [NSEntityDescription insertNewObjectForEntityForName:#"QuestionSet" inManagedObjectContext:[self managedObjectContext]];
qs.id = p_id;
qs.title = p_title;
for (int i = 0; i < [questions count]; i++)
{
NSFetchRequest *questionRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *questionEntity = [NSEntityDescription entityForName:#"Question" inManagedObjectContext:[self managedObjectContext]];
NSPredicate *questionPredicate = [NSPredicate predicateWithFormat:#"(id = %d)", [[[questions objectAtIndex:i] objectForKey:#"id"] intValue]];
[questionRequest setEntity:questionEntity];
[questionRequest setPredicate:questionPredicate];
Question *q = nil;
NSArray *questionObjects = [[self managedObjectContext] executeFetchRequest:questionRequest error:&error];
if (questionObjects == nil){
// Handle errors
}
if ([questionObjects count] > 0)
q = [questionObjects objectAtIndex:0];
else
q = [NSEntityDescription insertNewObjectForEntityForName:#"Question" inManagedObjectContext:[self managedObjectContext]];
q.id = [NSNumber numberWithInt:[[[questions objectAtIndex:i] objectForKey:#"id"] intValue]];
q.question = [[questions objectAtIndex:i] objectForKey:#"text"];
q.answer = [NSNumber numberWithInt:[[[questions objectAtIndex:i] objectForKey:#"ca"] intValue]];
for (int j = 0; j < [[[questions objectAtIndex:i] objectForKey:#"A"] count]; j++)
{
NSFetchRequest *answerRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *answerEntity = [NSEntityDescription entityForName:#"Answer" inManagedObjectContext:[self managedObjectContext]];
NSPredicate *answerPredicate = [NSPredicate predicateWithFormat:#"(id = %d)", [[[[[questions objectAtIndex:i] objectForKey:#"A"] objectAtIndex:j] objectForKey:#"id"] intValue]];
[answerRequest setEntity:answerEntity];
[answerRequest setPredicate:answerPredicate];
Answer *a = nil;
NSArray *answerObjects = [[self managedObjectContext] executeFetchRequest:answerRequest error:&error];
if (answerObjects == nil){
// Handle errors
}
if ([answerObjects count] > 0)
a = [answerObjects objectAtIndex:0];
else
a = [NSEntityDescription insertNewObjectForEntityForName:#"Answer" inManagedObjectContext:[self managedObjectContext]];
a.id = [NSNumber numberWithInt:[[[[[questions objectAtIndex:i] objectForKey:#"A"] objectAtIndex:j] objectForKey:#"id"] intValue]];
a.answer = [[[[questions objectAtIndex:i] objectForKey:#"A"] objectAtIndex:j] objectForKey:#"text"];
a.questions = [a.questions setByAddingObject:q];
q.answers = [q.answers setByAddingObject:a];
[answerRequest release];
}
q.questionsets = [q.questionsets setByAddingObject:qs];
qs.questions = [qs.questions setByAddingObject:q];
[questionRequest release];
}
[questionsetRequest release];
[[self managedObjectContext] save:&error];
[json_string release];
[self loadTrivia];
NSLog(#"After Load: %#", self.questionsets);
}
This function fetches all the QuestionSet objects from the db and stores the array in an ivar.
- (BOOL)loadTrivia
{
NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"QuestionSet" inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
self.questionsets = [[self managedObjectContext] executeFetchRequest:request error:&error];
return YES;
}
Just for completeness, here are my description methods in my classes
from QuestionSet.h
- (NSString *)description
{
return [NSString stringWithFormat:#"\
\n\tid: %#\
\n\ttitle: %#\
\n\tquestions: %#\n",
self.id,
self.title,
self.questions];
}
from Question.h
- (NSString *)description
{
return [NSString stringWithFormat:#"\
\n\tid: %#\
\n\tanswer: %#\
\n\tquestion: %#\
\n\tanswers: %#\n",
self.id,
self.answer,
self.question,
self.answers];
}
from Answer.h
- (NSString *)description
{
return [NSString stringWithFormat:#"\
\n\tid: %#\
\n\tanswer: %#\n",
self.id,
self.answer];
}
You shouldn't subclass the description method (Link):
Although the description method does not cause a fault to fire, if you implement a custom description method that accesses the object’s persistent properties, this will cause a fault to fire. You are strongly discouraged from overriding description in this way.
I tested your loadTrivia code (with a different entityName) on my app and it worked fine.
Did you have a look at the NSError (you should initialize it with =nil):
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
Also you should release the NSFetchRequest after execution in - (BOOL)loadTrivia.