Core Data storage from remote data source - ios

I am developing an iPhone app that gathers data from 3 separate feeds. In applicationDidFinishLaunching and applicationWillEnterForeground and Do the following:
[self emptySchedule];
[self populateSchedule];
[self emptyPlayers];
[self populatePlayers];
[self emptyNews];
[self populateNews];
The empty methods simply remove info from core data, and the populate methods add info back to core data by calling various web json/xml feeds. It seems to do this very fast; but was wondering if this is the preferred method for keeping information up to date in the app.
EDIT:
Just to give some context, here are a couple methods used for empty/populate:
Since this is mostly asynchronous will it affect application launch time?
- (void) emptySchedule
{
NSFetchRequest * allEvents = [[NSFetchRequest alloc] init];
[allEvents setEntity:[NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext]];
[allEvents setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * events = [self.managedObjectContext executeFetchRequest:allEvents error:&error];
//error handling goes here
for (NSManagedObject * event in events) {
[self.managedObjectContext deleteObject:event];
}
NSError *saveError = nil;
[self.managedObjectContext save:&saveError];
}
-(void)populateSchedule
{
NSURL *url = [NSURL URLWithString:SCHEDULE_FEED_URL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id schedule)
{
for (NSDictionary *campEvent in schedule)
{
Event *event = nil;
event = [NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
event.eventName = [campEvent valueForKeyPath:#"eventName"];
event.ticketsRequired = [campEvent valueForKeyPath:#"ticketsRequired"];
event.location = [campEvent valueForKeyPath:#"location"];
event.practiceStart = [NSDate dateWithTimeIntervalSince1970:[[campEvent valueForKeyPath:#"practiceStart"] doubleValue]];
event.practiceEnd = [NSDate dateWithTimeIntervalSince1970:[[campEvent valueForKeyPath:#"practiceEnd"] doubleValue]];
}
NSError *saveError = nil;
//Save inserts
[self.managedObjectContext save:&saveError];
//Notify other objects of this
[[NSNotificationCenter defaultCenter] postNotificationName:#"populateSchedule" object:nil];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Error Retrieving Data. Please try again later." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
}];
[operation start];
}

I'll try to answer based on my personal experience. Maybe someone else could have a different opinion on it.
In your case the syncing is performed only in phases of the application lifecycle.
So, I would add a third. When the user asks it. But it strictly depends on the nature of your application. Another way could be to set up a background thread that periodically wakes up and asks the server to send it new data. This could be more complex than the manually syncing.
About these two ways I would perfom import operation in specific background threads. You could set up your own operations (NSOperation class is there also for this type of task) and do stuff there or use new iOS 5 Queue Core Data API.
If you haven't done already (the background importing) do it also for your actual methods (I think in this case you could just unify the pairs empty/populate). This mechanism allows you to boost application startup and say to the user what is going on without freeze the UI:"Hello, I'm fetching data from the server! Please wait".
Edit
About the code you added it's ok for me. Only two considerations.
First, if the deletion is performed in the main thread, it could block the main thread if you have a lot of entries to remove. In this case, the UI could be not responsive. Anyway you have done a good job setting setIncludesPropertyValues to NO.
About the other snippet, I guess only the data download is perfomed in an asynchronous fashion. The completion handler is performed in the main thread (you can check for example with BOOL isMainThread = [NSThread isMainThread]) and so the core data object creation and its relative saving. Also in this case if you have a lot of data the main thread could be blocked.
Anyway, if you have done some tests and the app doesn't take too long to startup, you could just remain with your code. If you start to see some sort of latency, maybe you could do Core Data operations in backgrounds.
With no iOS 5 API the save call could (I say could since you can save chuncks of data and not the whole one) take time to be performed (in particular when you have a lot of objects to store in your core data file). Starting form iOS 5 you could take advantage of new type of NSManagedObjectContext (queue concurrency type) and parent-child context. Furthermore you can avoid to write the entire Core Data stack and use the UIManagedDocument class. By means of both the save can be performed in a concurrent queue without blocking the main thread.
Hope that helps.

Related

Dispatch 100 HTTP Request in order

I am using objective-C to write an app which needs to dispatch 100 web request and the response will be handled in the call back. My question is, how can I execute web req0, wait for call back, then execute web req1 and so on?
Thanks for any tips and help.
NSURL *imageURL = [[contact photoLink] URL];
GDataServiceGoogleContact *service = [self contactService];
// requestForURL:ETag:httpMethod: sets the user agent header of the
// request and, when using ClientLogin, adds the authorization header
NSMutableURLRequest *request = [service requestForURL:imageURL
ETag: nil
httpMethod:nil];
[request setValue:#"image/*" forHTTPHeaderField:#"Accept"];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
fetcher.retryEnabled = YES;
fetcher.maxRetryInterval = 0.3;
fetcher.minRetryInterval = 0.3;
[fetcher setAuthorizer:[service authorizer]];
[fetcher beginFetchWithDelegate:self
didFinishSelector:#selector(imageFetcher:finishedWithData:error:)];
}
- (void)imageFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
if (error == nil) {
// got the data; display it in the image view. Because this is sample
// code, we won't be rigorous about verifying that the selected contact hasn't
// changed between when the fetch began and now.
// NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
// [mContactImageView setImage:image];
NSLog(#"successfully fetched the data");
} else {
NSLog(#"imageFetcher:%# failedWithError:%#", fetcher, error);
}
}
You can't simply call this code in a loop as GTMHTTPFetcher works asynchronously so the loop, as you see, will iterate and start all instances without any delay.
A simple option is to put all of the contacts into a mutable array, take the first contact from the array (remove it from the array) and start the first fetcher. Then, in the finishedWithData callback, check if the array contains anything, if it does remove the first item and start a fetch with it. In this way the fetches will run serially one after the other.
A better but more complex solution would be to create an asynchronous NSOperation (there are various guides on the web) which starts a fetch and waits for the callback before completing. The benefit of this approach is that you can create all of your operations and add them to an operation queue, then you can set the max concurrent count and run the queue - so you can run multiple fetch instances at the same time. You can also suspend the queue or cancel the operations if you need to.

App lags on huge Core Data mappings via RestKit & Magical Record

Im still trying to figure out what loads the UI thread. In a class(a child of UITableView) there's a FRC:
NSFetchRequest *request = [DEPlace MR_requestAllWithPredicate:[NSPredicate predicateWithFormat:#"isWorking == YES"]];
request.sortDescriptors = #[ [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES] ];
self.placesController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:[NSManagedObjectContext MR_rootSavingContext]
sectionNameKeyPath:nil
cacheName:nil];
self.placesController.delegate = self;
It used to be attached to a MR_contextForCurrentThread. Changing it to rootSavingContext slightly affected the performance. Then i set both root and default contexts to the same one:
[NSManagedObjectContext MR_setRootSavingContext:managedObjectStore.persistentStoreManagedObjectContext];
[NSManagedObjectContext MR_setDefaultContext:managedObjectStore.persistentStoreManagedObjectContext];
Default context used to be set to mainQueueManagedObjectContext. I want to move literally everything core data related to background and let FRC take care of interactions with the UI. FRC delegate gets new data by:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
//self.places = [self sortPlaces:controller.fetchedObjects];
self.places = controller.fetchedObjects;
[self.delegate contentUpdatedInDatasource:self];
}
I disabled the sorting by now, thought it could affect the main thread. I've tried figuring out what else could load the main thread with Time Profiler, but didn't find anything suspicious. screenshot
When all the data is loaded everything run smoothly, the app lags only at the first start, when the DB gets populated. Since everything loading-related is held by RestKit i don't think it causes problems.
I was thinking of delaying requests by 10 per second max, but have no idea how can i achieve it. Basically, on the start app gets and array of IDs(~250 by now) and then looping trough the array and requesting data from the server by each ID. It's not so crucial so far, but when the array grow up to 1-2k it would be a big problem. Btw, a single data object has 4 relationships in the DB. Is reducing dependencies a possible solution?
UPDATE:
I've tried to split the request to 1 by 1 and it caused a pretty weird behaviour.
For some reason there's a huge delay between requests.
This is how i get an array of IDs
AFJSONRequestOperation *op = [[AFJSONRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[APIRoot stringByAppendingFormat:#"/venues/listId?%#=%#&%#=%#", TokenKey, [DEUser token], UDIDKey, [DEUser udid]]]]];
// dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.name.bgqueue", NULL);
op.successCallbackQueue = backgroundQueue;
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//gettin an array of IDs
NSArray *array = (NSArray*) responseObject;
if(array.count)
{
_array = array;
[self getVenuesFromSelfArrayWithCurrentIndex:0];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"3rr0r: %#", error);
}];
[[NSOperationQueue mainQueue] addOperation:op];
And this is a code of recursive method:
- (void)getVenuesFromSelfArrayWithCurrentIndex: (NSUInteger)index
{
if(index >= _array.count){ NSLog(#"loading finished!"); return; }
//version of the app, location e.t.c.
NSMutableDictionary *options = [[self options] mutableCopy];
[options setObject:[_array objectAtIndex:index] forKey:#"venueId"];
//method below calls RKs getObjectsAtPath, and it's pretty much the only thing it does
[[DEAPIService sharedInstance] getObjectsOfClass:[DEPlace class]
withOptions:options
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
NSManagedObject *object = [mappingResult.array firstObject];
if([object isKindOfClass:[DEPlace class]])
{
[self getVenuesFromSelfArrayWithCurrentIndex:index+1];
}
} failure:^(RKObjectRequestOperation *operation, NSError *error){
NSLog(#"Failed to load the place with options: %#", options.description);
[self getVenuesFromSelfArrayWithCurrentIndex:index+1];
}];
}
The weird part is that it takes ~1-2 seconds(!) to start next request and cpu usage log and threads look.. strange.
Screenshot 1
Screenshot 2
Any suggestions?
At this point in time I can only suggest around the 250 requests. You can't make more than around 4 or 5 concurrent network requests without flooding the network and grinding it to a halt on a mobile device. Really you should change the web service design so you can send batch requests as this is a lot more efficient both for the client and the server.
Anyway, you can limit the concurrent requests by setting the maxConcurrentOperationCount of the operationQueue of your object manager. The recommendation would be to set it to 4.

UIView does not get updated correctly after setting UIImage and UITextField

I have some data that I am getting from a server and then displaying in my UIViewController class. To do this, I have two classes. The UIViewController and another one named ServerCommunicator. UIViewController is the delegate for ServerCommunicator class. The serverCommunicator looks as follows:
- (void)fetchServerData:(NSString *) serverAddress{
NSURL *url = [[NSURL alloc] initWithString:serverAddress];
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
[self.delegate fetchingSongsFailedWithError:error];
} else {
[self.delegate receivedSongsJSON:data];
}
}];
}
The UIViewController allocates the serverCommunicator, sets itself as delegate and then issue the fetch request.
- (void)viewDidLoad {
[super viewDidLoad];
self.songServerCommunicator = [[serverCommunicator alloc] init];
self.songServerCommunicator.delegate = self;
[self.songServerCommunicator fetchServerData:<some_server_ip>];
}
After it does that it implements the required protocol method:
- (void)receivedSongsJSON:(NSData *)data{
NSLog(#"received server response");
/* Parses the data and displays in textfield/imageview */
}
My problem is that when I do display the data received in the delegate method, it doesn't get reflected right away in the UI. It is very weird, sometimes it gets shown 20 seconds laters on its own, other times it takes like a minute. I am not sure whats going on. I know for a fact that the data was fetched right away because the logged message gets printed way before the UIView gets updated.
Thanks for any help on this.
Make sure you are on the main thread when you update the UI
Other people have pointed out the problem, but they did not provide the solution in concrete code. This is the coded solution:
- (void)fetchServerData:(NSString *) serverAddress{
NSURL *url = [[NSURL alloc] initWithString:serverAddress];
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
dispatch_async(
dispatch_get_main_queue(),
^void {
if (error) {
[self.delegate fetchingSongsFailedWithError:error];
} else {
[self.delegate receivedSongsJSON:data];
}
}
);
}];
}
You must understand how Grand Central Dispatch works in iOS. GCD is an abstraction layer for multithreading.
The main queue, which your UI runs on, is a serial queue. That means you can't have two blocks of code running at the same time. It would be poor user experience if you were to do a long network query on the main queue, because it would prevent any UI code from running. This would make the app appear like it is frozen to the user.
To solve the freezing UI issue, iOS gives you other queues to do work without blocking up the main queue. iOS provides ways to create your own custom queues or use pre-made global concurrent queues. The queues that NSURLConnection uses is not known to us because we don't have the Cocoa source code. But what we do know is that NSURLConnection is definitely not using the main queue because UI is not frozen while it is grabbing data from a server.
Since NSURLConnection is running on a different thread than what the main queue (UI) runs on, you cannot just update UI at any random time. This is a common problem with multithreaded programs. When multiple threads access the same block of code, instructions may be interleaved and both blocks get unexpected results. One of the unexpected results that you experienced was the 20 second delay of the UI finally being updated. To prevent interleaving, you need to run all the UI code on the same thread. You do this by enqueuing your UI update code to the end of the main queue.
The method receivedSongsJSON() is called from a callback given to [NSURLConnection sendAsynchronousRequest] which I think is being called from a background thread.
Even if the method receivedSongsJSON() is declared in your UIViewController it will be executed in background thread if it is called from one.
As #robdashnash has indicated make sure you call all the UI updating code from main thread. If you are not sure how to do that please check the documentation of Grand Central Dispatch (GCD) here.

MagicalRecord + AFNetworking + NSFetchedResultsController, how to make it work?

In my application I try to use MagicalRecord + AFNetworking + NSFetchedResultsController together, to synchronize data and dynamically display it on map or in tableView.
Let's see some code for download method:
+ (void) getDataWithCompletionBlock: (void (^)(void)) block {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL_GET_DATA]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
for (NSDictionary *dict in JSON) {
[MyModel createOrUpdateMyModelFromDict:[dict mutableCopy]];
}
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveInBackgroundCompletion:^{
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveNestedContexts];
block();
}];
});
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
DDLogError(#"getDataWithCompletionBlock FAILURE: %#", error);
}];
[operation start];
}
I download data using AFJSONRequestOperation, then create models using GCD and background thread, save context for current thread and perform successBlock (MagicalRecord runs successBlock on dispatch_get_main_queue(), so it is called in GUI thread.
Is this synchronization model ok ? Because sometimes (more often on real device than simulator) I get some errors from NSFetchedResultsController like "no object at index: in section at index:" or "CoreData could not fulfill a fault...".
They all suggest, there is something wrong with Core Data and multithreading environment. Did anyone try to connect all these three tools to work together ? If so, what am I missing ? Do you have any good-working code samples for that architecture ?
I would suggest using:
[MagicalRecord saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block];
It handles threading for you so you don't have to worry about anything getting saved on the parent context from a background context.
I highly encourage you to read this blog post (written by the creator of MagicalRecord Saul Mora): IMPORTING DATA MADE EASY. It super good background information about the workings of MagicalRecord and an example of how to use it.
If you setup your Data Model correctly you can actually have MagicalRecord do all the mapping for you and all you have to do is call importFromObject: inside the saveWithBlock: block and you don't have to worry about dealing with the mapping. The article I linked to above goes into detail about how to do it properly, but it will take some practice.

Unresponsive TableView while WebRequest (AFNetworking) on device, but ok in simulator + MagicalRecord issue

I have an application that retrieves json (employees workschedules) from a web service using AFNetworking and displays them in a table view.
I have my webservice class that takes care of doing the request and once it is done, it stores these data into coredata (I have an another issue here, being that I use magicalRecord and the data does not persist, and I don't understand why) and then calls back its delegate (my tableViewController) telling it it's done, so this can load the workschedules into the cells.
WebServiceClient.m
NSURL *url = [NSURL URLWithString:stringUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
NSArray *workSchedules = [[[NSSet alloc] initWithArray:JSON] allObjects];
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
Workschedule *workscheduleEntity = nil;
NSError *error = nil;
for (NSDictionary *web_workschedule in workSchedules)
{//Inside this method I create other entities that will hydrate my workschedule entity, and it is done using the MR_CreateInContext
workscheduleEntity = [Workschedule workScheduleFromJSONDictionary:web_workschedule withError:&error];
[context MR_save];
}
if([self.delegate respondsToSelector:#selector(workSchedules)]){
[self.delegate workSchedules];
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
LOG_ERROR(2,#"Received an HTTTP %d:", response.statusCode);
LOG_ERROR(2,#"The error was: %#", error);
if([self.delegate respondsToSelector:#selector(workSchedules:)]){
[self.delegate workSchedules:nil];//return error
}}];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:operation];
}
PendingWorkscheduleViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.webServiceClient getMockedWorkSchedulesForEmployee:[NSNumber numberWithInt:1]];
[self workSchedules];
}
-(void)workSchedules
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"pending == YES"];
NSArray *pendingWorkSchedules = [Workschedule MR_findAllWithPredicate:predicate];
self.pendingWorkSchedules = pendingWorkSchedules;
[self.tableView reloadData];
}
My problem is that when i run this while the request is processed the UI is unresponsive (it's a very brief time, but if the request were to increase...) so that if i load the table view and right away try to scroll or click the back button, it just ignores it as it is "frozen". This behavior is on my iphone 4s. On the simulator this works fine and I can't wrap my head around why is that. I tried to call the "[self.webServiceClient getMockedWorkSchedulesForEmployee:[NSNumber numberWithInt:1]];" in a queue using GCD, I tried using performSelectorInBackground: WithObject: etc but still the same (even though with this last method it seemed a little more efficient, but it's an impression and only on the simulator, no changes on the device).
As far as magicalRecord goes I will make separate question.
I would appreciate your help.
Fixed it. The problem is that the success block run on the main thread! (which I did not understand). I just used GCD in the success block with a background queue for processing the data and the main queue to store this data in core data.
As far as magical record issue, i needed to save "nestedContext".
Cheers everyone.

Resources