I am attempting to load a collection of objects via RestKit and have found that this operation is blocking the main thread. Specifically, this functionality is called when a UIViewController appears in the -(void)viewDidAppear:(BOOL)animated; message. As long as request is loading, the user is unable to interact with the UI, and has to wait until the operation has completed.
How can I instruct RestKit to execute the request asynchronously (I thought it already was) and stop blocking the main thread?
-(void)rtrvArtistsByStudio:(ISStudio *)studio
{
NSLog(#"Retrieving Artists for studio %ld", [studio studioID]);
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *params = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%ld", [studio studioID]] forKey:#"studioID"];
NSURLRequest *request = [restkit requestWithObject:nil method:RKRequestMethodGET path:#"/restAPI/rtrvArtistsByStudio" parameters:params];
RKManagedObjectRequestOperation *operation = [restkit managedObjectRequestOperationWithRequest:request managedObjectContext:restkit.managedObjectStore.persistentStoreManagedObjectContext success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray *artists = [mappingResult array];
[studio setArtists:artists];
[notificationCenter postNotificationName:nStudioLoadedArtists object:studio];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed to load artists for studio %ld due to %#", [studio studioID], error);
[notificationCenter postNotificationName:nStudioFailedLoadingArtists object:studio];
}];
[restkit enqueueObjectRequestOperation:operation];
}
EDIT:
I should also note that restkit is a pointer to an instance of RKObjectManager.
RestKit version is 0.20
The request is definitely being dispatched asynchronously. If you press the pause button in the Xcode UI and look at the threads, what is happening on the main thread and the background threads during the request? That should provide insight into what is causing your thread blockage.
Turns out that it was actually my own fault (of course). I have a UIViewController category that performs some transitions. During the transition I have the UIApplication ignore touch events until the transition has completed (0.2s, very brief). I completely forgot that this existed.
Because I am loading some remote objects in viewDidAppear it was still ignoring touch events until that completed. Removing the touch ignore fixed the problem.
Related
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.
I am trying to figure out how long my round trip to the server is before any mapping occurs. I need to get at the RKObjectRequestOperation, but it is only available in the success and fail blocks.
I see that RestKit 2 does send a notification:
[[NSNotificationCenter defaultCenter] postNotificationName:RKObjectRequestOperationDidStartNotification object:weakSelf];
But there is no user info sent along.
Any ideas on how I can do this? I was thinking of an associated object onto the operation queue but that is causing crashes.
What I did:
self.op = self.objectManager.operationQueue.operations.lastObject;
objc_setAssociatedObject(self, &kRetrieverRequestOperationKey, self.op, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
Added the above right after:
[self.objectManager getObjectsAtPath:resourcePath parameters:parmsDictionary success:^(RKObjectRequestOperation *requestOperation, RKMappingResult *mappingResult)
{
[weakSelf didLoadOperation:requestOperation result:mappingResult isFromCache:NO];
[weakSelf requestDidEnd:requestOperation];
} failure:^(RKObjectRequestOperation *requestOperation, NSError *error) {
[weakSelf requestOperation:requestOperation didFailWithError:error];
[weakSelf requestDidEnd:requestOperation];
}];
Then when RestKit posted its notification I was able to get at the RKObjectRequestOperation.
Not ideal, but seems to work.
I have a NSOperation that I put in a queue. The NSOperation does some long running photo processing then I save the information/meta data in core data for that photo. In the main method of my custom NSOperation class is where I execute the below block of code
-(void)main
{
//CODE ABOVE HANDLES PHOTO PROCESSING...
//........
//THEN I SAVE ALL DATA BELOW LIKE SO
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Post *post = [Post createInContext:localContext];
//set about 15 pieces of data, all strings and floats
post.XXXX = XXXXX;
post.DDDD = DDDDD;
etc...
} completion:^(BOOL success, NSError *error) {
NSLog(#"Done saving");
}];
}
My issue is that even with only 3 photos when it saves it really freezes my UI. I would have thought executing this in the NSOperation I would be fine.
I should add that each NSOperation processes one photo, so at times the queue could have 5-10 photos, but I would not think this would make any difference, even with just three like I said its freezing the UI.
Thank you for the help.
UPDATE:------------*--------------
I switched to version 2.2 but that seems to be blocking the UI even more...also now I'm using
-(void)main
{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
//CODE BELOW HANDLES PHOTO PROCESSING...
//........
//THEN I SAVE ALL DATA BELOW LIKE SO
Post *post = [Post createInContext:localContext];
//set about 15 pieces of data, all strings and floats
post.XXXX = XXXXX;
post.DDDD = DDDDD;
[localContext saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
}];
}
This is all done in my NSOperation class, am I doing something wrong?
Don't put the saveWithBlock calls in a background thread. You're effectively creating a background thread from a background thread, which, in this case, is just slowing you down. You should just be able to call saveWithBlock and it should put all your saving code in the background. However, I'm also noticed that you make all your changes in the main UI page of the code, and only call save afterward. This is the wrong usage of this method. You want to do something more like this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Post *post = [Post createInContext:localContext];
//photo processing
//update post from photo processing
} completion:^(BOOL success, NSError *error) {
//This is called when data is in the store, and is called on the main thread
}];
If you do need an NSOperation, I suggest a different pattern:
- (void) main {
NSManagedObjectContext *localContext = [NSManagedObjectContext confinementContext];
// Do your photo stuff here
Post *post = [Post createInContext:localContext];
//more stuff to update post object
[localContext saveToPersistentStoreAndWait];
}
Be careful in how you start the operation.
[operation start]
will start the operation on the current thread, so if you call it from the main thread (which is the UI one) it will block the interface.
You should add the operation to a queue, so that it runs in background without hogging the main thread
[[NSOperationQueue new] addOperation:operation];
I am working on an iOS application that will use RestKit 0.20 to make REST-based calls to a service that is running on JBoss AS 7.1.1 and using restEASY as its REST-based web service framework.
The REST service that the client app will be calling is used to retrieve objects based on their unique identifier. Since these objects can be small or large (> 1MB in size) and great in number (20? 50? 100 or more at a time) I don't want to make one large call to retrieve them all at once. Rather, I was planning on using RestKit's queued operation support to create a GET request for each object based on the object identifier, and execute the calls asynchronously. Once the GET has completed, each object will be processed through the use of Objective-C blocks so as to avoid any unnecessary blocking.
My RestKit client code looks like this...
NSArray *identifiers = ...
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKResponseDescriptor *getObjResp = [RKResponseDescriptor responseDescriptorWithMapping:[MyObject mapping] pathPattern:[WebServiceHelper pathForServiceOperation:#"/objects/:uniqueIdentifier"] keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
for (int i=0; i < identifiers.count; i++) {
NSString *identifier = [identifiers objectAtIndex:i];
NSURL *serviceURL = [WebServiceHelper urlForServiceOperation:[NSString stringWithFormat:#"/objects/%#", identifier]];
NSURLRequest *request = [NSURLRequest requestWithURL:serviceURL];
RKObjectRequestOperation *requestOp = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[getObjResp]];
[requestOp setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
MyObject *obj = [mappingResult firstObject];
if (self.delegate != nil) {
[self.delegate didLoadObjectWithIdentifier:identifier myObj:obj];
}
} failure:^(RKObjectRequestOperation *operation, NSError *error){
if (self.delegate != nil) {
[self.delegate didFinishWithError:error];
}
}];
[objectManager enqueueObjectRequestOperation:requestOp];
}
From there, the delegate method that gets called when an object has been retrieved looks like this:
-(void)didLoadObjectWithIdentifier:(NSString *)identifier myObj:(MyObject *)myObj {
if(secureMessage != nil) {
NSLog(#"Message %# retrieved successfully : %#:%#", identifier, myObj);
} else {
NSLog(#"NO OBJ");
}
}
The calls appear to be functioning as expected, as I am able to print out information about the retrieve objects. However, I am seeing some weird/unexepcted behavior on the service side.
First, I see a number of Exceptions being thrown by restEASY:
13:22:02,903 WARN [org.jboss.resteasy.core.SynchronousDispatcher] (http--0.0.0.0-8080-10) Failed executing GET /objects/BBFE39EA126F610C: org.jboss.resteasy.spi.WriterException: ClientAbortException: java.net.SocketException: Broken pipe
at org.jboss.resteasy.core.ServerResponse.writeTo(ServerResponse.java:262) [resteasy-jaxrs-2.3.2.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.writeJaxrsResponse(SynchronousDispatcher.java:585) [resteasy-jaxrs-2.3.2.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:506) [resteasy-jaxrs-2.3.2.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:119) [resteasy-jaxrs-2.3.2.Final.jar:]
at org.jboss.seam.resteasy.ResteasyResourceAdapter$1.process(ResteasyResourceAdapter.java:145) [jboss-seam-resteasy.jar:2.3.0.Final]
at org.jboss.seam.servlet.ContextualHttpServletRequest.run(ContextualHttpServletRequest.java:65) [jboss-seam.jar:2.3.0.Final]
at org.jboss.seam.resteasy.ResteasyResourceAdapter.getResource(ResteasyResourceAdapter.java:120) [jboss-seam-resteasy.jar:2.3.0.Final]
...
It would appear as though RestKit is closing the socket somehow (or some other error is preventing the object from being read from the server). I am unable to find anything in the documentation that could explain what is going on here.
Secondly, though, I also see another call for the very same object when a request fails with this error. Why is the GET being called more than once? Is RestKit redoing the failed GET request?
I'm mostly concerned about why the Exception is occurring within restEASY, as it will make it difficult to diagnose calls that really do fail. Has anyone seen this behavior before? Any tips as to how I can correct these issues? Thanks for any help you can give!!
Those exception are resulted from disconnected Clients i.e. some of the users might quit the app while waiting for the process to complete OR has a network failure (at the client end).
Hence, Broken Pipe.
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.