iOS: how to execute an high number of reverseGeocoding request? - ios

i have to execute a high number of reverseGeocodeLocation request,
i use this method for doing that:
for (Photo *photo in arrayWhitPicture) {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[queue addOperationWithBlock:^{
[geocoder reverseGeocodeLocation:[[CLLocation alloc] initWithLatitude:[photo.latitude doubleValue] longitude:[photo.longitude doubleValue]] completionHandler:^(NSArray *placemarks, NSError *error) {
if (error){
NSLog(#"Geocode failed with error: %#", error);
return;
}
CLPlacemark *myPlacemark = [placemarks objectAtIndex:0];
NSString *city = myPlacemark.locality;
NSLog(#"My country code: %#", city);
}];
}];
}
this code actual work, but the problem is that some of these request (like half) get this error:
Error Domain=kCLErrorDomain Code=2
after a few research i think this happen because i do a lot of request in short amount of time in fact the apple documentations say:
Send at most one geocoding request for any one user action.
When you want to update the user’s current location automatically
(such as when the user is moving), issue new geocoding requests only
when the user has moved a significant distance and after a reasonable
amount of time has passed. For example, in a typical situation, you
should not send more than one geocoding request per minute.
so my question is: my error is really caused by the fact that i do a lot of request, and in that case what can i do to resolve this problem, do you know other system apart to use the reverseGeocoding?

I have had exactly this problem, and my solution was to throttle down the number of reverse geocode queries that I performed (i.e. temporarily suspend the queue if already processed a whole bunch). This worked so well that I did not have to implement plan B, which was to switch to a different service.
See for example this question for discussion of alternate services. Google has a similar limit of 2500 requests per API key and 24 hour period. There is also Bing.

Apple most definitely limits how many geocoding requests you can issue at a time. Other users are reporting that the limit is around 50, though that could change at any time. The recommendation seems to be to do the geocoding in batches and to issue only one batch at a time, starting each batch only after the previous one completes.

Apple limits to perform number of Reverse GeoCode requests your application can make at a time. Sometimes I have seen this limited to ONE.
The solution is to implement your own Reverse Geocoder Queue (you can implement it as a separate class), in which you can add all of your requests. This queue need to execute one request at a time and after first is done, execute next. You can add callback blocks to notify you once the reverse geocoding is done for each request.
Example API in the Reverse Geocoder queue class can be like:
- (void) reverseGeocodeLocation: (CLLocation *) location completion: (CLGeocodeCompletionHandler) completionHandler
{
// Create some queue (NSMutableArray) in the class
// Create some ReverseGeoLocationObject with location and completionHandler as members
// Add ReverseGeoLocationObject to queue
// Check is queue is not already processing. If NO then process next request. You have have API named processNextRequest which you can call here (put code you posted in this API for single request)
}
Also call processNextRequest when CLGeocoder returns.

Related

MKDirections calculateETAWithCompletionHandler: in background state

I have an app which monitors significant location changes.
Upon receiving a new calculation I want to calculate the duration from the current location to a specified location.
To calculate the duration I use calculateETAWithCompletionHandler: from the MKDirections class.
Everything works as expected as long as the app is in the foreground.
When I send the app to the background, it is correctly receives location updates in the background and everything works until I call calculateETAWithCompletionHandler:, which will never return results.
MKDirectionsHandler, the completion handler of calculateETAWithCompletionHandler:. is never called when being in the background.
As soon as the app is coming into the foreground again, all the waiting completion handlers are receiving results.
MKMapItem* origin = [MKMapItem mapItemForCurrentLocation];
MKMapItem* destination = [[MKMapItem alloc] initWithPlacemark:destinationPlacemark];
MKDirectionsRequest* request = [MKDirectionsRequest new];
[request setSource:origin];
[request setDestination:destination];
[request setTransportType:MKDirectionsTransportTypeAutomobile];
MKDirections* directions = [[MKDirections alloc] initWithRequest:request];
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {
completion(response.expectedTravelTime, error);
}];
Is calling calculateETAWithCompletionHandler: in the background not allowed?
Is there any way to resolve this issue?
I believe the way you are making use of MKMapItem is the problem, you need to run this on the main thread. So I don't think it will work for what you need. When collecting the location in the background you should use CoreLocation instead.
The documentation around MKDirection is not very specific on this issue, the most relevant section I could find was:
An MKDirections object provides you with route-based directions data from Apple servers. You can use instances of this class to get travel-time information or driving or walking directions based on the data in an MKDirectionsRequest object that you provide. The directions object passes your request to the Apple servers and returns the requested information to a block that you provide.
Since you are trying to calculate travel-time, it would appear that calculateETAWithCompletionHandler: tries to perform a network request to the apple servers. With the application being in a background state, the request is put on hold until the application enters foreground again.
Unfortunately I don't think there is an easy way around this. You could try and use a "guesstimation" approach where, before the application enters a background state it calculates the ETA for a user, and then while it is in the background it increases or decreases the ETA proportionally to the direct distance between your current location and the destination. Depending on how precise you want your results to be this broad estimation could be enough to satisfy your requirements.

MKMapView User Location Timeout using Parse.com

So I am using parse to get an update of the users location and data from that query like so:
-(void) getUserLocationAndData:(BOOL) showProgress
{
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError *error)
{
if (!error)
{
NSLog(#"Got current location - Zooming Map!");
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = geoPoint.latitude;
zoomLocation.longitude = geoPoint.longitude;
[self getNearByData:geoPoint];
}
else
{
[[[UIAlertView alloc] initWithTitle:#"Unable to get Current Location" message:#"This app needs your current location in order to locate near by data. Please check your internet connection and make sure you enabled access to the location information." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil] show];
}
}];
}
When the app first launches the user location shows up, the data is pulled and everything works. If you wait about 15 seconds and don't move the user location icon turns gray (see image) and any updates result in a failure. If I reload the view it works again but the same thing happens.
I am not using a CLLocationManager because I do not need to constantly pull the data near the user. Just every so often or on demand by the user.
I have also noticed if I wait long enough the user location goes Blue again and all seems to work. What is causing this timeout? Can I set this timeout, or do I need to just use a CLLocationManager to have any control of this? Is Parse just timing out with an internal CLLocationManager or something?
Thanks for the help!
So evidently when you use the Parse function: geoPointForCurrentLocationInBackground Parse will take over with it's own internal CLLocationManager. After it does it's initial thing it stops updating the location (or possibly only updates the user location if the user moves enough.. again just a guess here.. and if that feature is supported on the device).
The solution is to create your own CLLocationManager in your view controller and instead of calling
geoPointForCurrentLocationInBackground
You simply store the updated user location and call:
PFGeoPoint *usersGeoPoint = [PFGeoPoint geoPointWithLocation:usersLastKnownLocation];
And now we can use that to query near by data. We are now responsible for starting and stopping the updating of the user location. I strongly recommend using this post to setup your own LocationManager and be sure to scroll down to the updated answers for more up to date info.
How can I get current location from user in iOS
After reading about the geoPointWithLocation in the Parse docs I made the conclusions above. Please read it for yourself (I know it's really hard to find and just one line...)

Time Profiler for asynchronous requests

Hi am try to figure out how to use Time Profiler for asynschronous requests. Currently I use NSDate and NSTimeInterval to track the time it takes for a request to return. Here is the code for what I am doing now:
NSDate * start = [NSDate date];
[FBRequestConnection startWithGraphPath:[NSString stringWithFormat: #"%#/photos?fields=images", albumName] completionHandler:^(FBRequestConnection *connection, FBGraphObject *result, NSError *error) {
NSTimeInterval timeBetweenDates = [[NSDate date] timeIntervalSinceDate:start];
NSLog(#"Facebook Album Request Time: %f", timeBetweenDates);
...
Seems like there should be a way to do this Time Profiler. Does time profiler provide this a way to do this? Also, how do I get the profile of events taking place running inside the async request?
No, Time Profiler doesn't measure the amount of time that has passed between two points in your code. It samples what your threads are doing at various points. Over a given sampling period, it tells you how your threads allocated their time which is a different question than how much time elapsed between two points in your code. Also, it's statistical in nature.
You can use the DTPerformanceSession framework to start and stop profiling or emit flags, but I'm not sure that will achieve what you're looking for either.
As to "how do I get the profile of events taking place running inside the async request", the time profiler should already be effectively showing that. However, you should recognize that the answer may be "nothing". Async APIs are usually asynchronous precisely because they have to wait for some external events and, while they're waiting, your code or even the frameworks are doing nothing. For example, if a request is sent to a remote server and the frameworks are waiting for a reply, they will be idle until the server sends the reply back.

(iOS) Offline Sync DB - Server

Trying to implement an app which sends offline data stored on local db to web server when connected to internet. I use the code shown below. As far I have tested it works fine, not sure it will work fine for huge number of records. I would like to know whether any tweaking on this code may increase the performance???
NOTE
I know this would be a worst code for offline sync purpose, so trying
to tweak it better.
Its a single way synchronization, from app to server.
-(void)FormatAnswersInJSON {
DMInternetReachability *checkInternet = [[DMInternetReachability alloc] init];
if ([checkInternet isInternetReachable]) {
if ([checkInternet isHostReachable:#"www.apple.com"]) {//Change to domain
responseArray = [[NSMutableArray alloc] init];
dispatch_async(backgroundQueue, ^(void) {
NSArray *auditIDArray = [[NSArray alloc] initWithArray: [self getUnuploadedIDs]];
for (int temp = 0; temp < [auditIDArray count]; temp ++) {
// Code to post JSON to server
NSURLResponse *response;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!error) {
NSString *responseID = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
if ([responseID isEqualToString:#"ERROR"]) {
//Error uploading records
} else {
[responseArray addObject:responseID];
}
} else {
//Error
return;
}
}
dispatch_async( backgroundQueue, ^{
/* Based on return code update local DB */
for (int temp = 0; temp < [responseArray count]; temp ++) {
[self updateRecordsForID:[auditIDArray objectAtIndex:temp] withID:[responseArray objectAtIndex:temp]];
}
});
});
}
}
}
- (void)upload { //Called when internet connection available
if(backgroundQueue){
dispatch_suspend(backgroundQueue);
dispatch_release(backgroundQueue);
backgroundQueue = nil;
}
backgroundQueue = dispatch_queue_create("com.XXXX.TestApp.bgqueue", NULL);
dispatch_async(backgroundQueue, ^(void) {
[self FormatAnswersInJSON];
});
}
If this code were sitting in front of me, my approach would be:
Look at the use cases and define 'huge number of records': Will 50 record updates at a time occur regularly? Or will it be in 1s and 2s? Do my users have wifi connections or is it over the paid network?, etc.
If possible, test in the wild. If my user base was small enough, gather real data and let that guide my decisions, or only release the feature to a subset of users/beta tests and measure.
If the data tells you to, then optimize this code to be more efficient.
My avenue of optimization would be doing group processing. The rough algorithm would be something like:
for records in groups of X
collect
post to server {
on return:
gather records that updated successfully
update locally
}
This assumes you can modify the server code. You could do groups of 10, 20, 50, etc. all depends on the type of data being sent, and the size.
A group algorithm means a bit more pre-processing client side, but has the pro of reducing HTTP requests. If you're only ever going to get a small number of updates, this is YAGNI and pre-mature optimization.
Don't let this decision keep you from shipping!
Your code has a couple of issues. One convention is to always check the return value before you test the error parameter. The error parameter might be set - even though the method succeeded.
When using NSURLConnection for anything else than a quick sample or test, you should also always use the asynchronous style with handling the delegate methods. Since using NSURLConnection properly may become quickly cumbersome and error prone, I would suggest to utilize a third party framework which encapsulates a NSURLConnection object and all connection related state info as a subclass of NSOperation. You can find one example implementation in the Apple samples: QHTTPOperation. Another appropriate third party framework would be AFNetworking (on GitHub).
When you use either the async style with delegates or a third party subclass, you can cancel the connection, retrieve detailed error or progress information, perform authentication and much more - which you can't with the synchronous API.
I think, once you have accomplished this and your approach works correctly, you may test whether the performance is acceptable. But unless you have large data - say >2 MByte - I wouldn't worry too much.
If your data becomes really large, say >10 MByte you need to consider to improve your approach. For example, you could provide the POST data as file stream instead a NSData object (see NSURLRequest's property HTTPBodyStream). Using a stream avoids to load all the POST data into RAM which helps alleviate the limited RAM problem.
If you have instead smaller POST data, but possibly many of them, you might consider to use a NSOperationQueue where you put your NSOperation connection subclass. Set the maximum number of concurrent operations to 2. This then may leverage HTTP pipelining - if the server supports this, which in effect reduces latency.
Of course, there might be other parts in your app, for example you create or retrieve the data which you have to send, which may affect the overall performance. However, if your code is sound and utilizes dispatch queues or NSOperations which let things perform in paralel there aren't many more options to improve the performance of the connection.

Stopping an NSOperationQueue

I have an NSOperationQueue that handles importing data from a web server on a loop. It accomplishes this with the following design.
NSURLConnect is wrapped in an NSOperation and added to the Queue
On successful completion of the download (using a block), the data from the request is wrapped in another NSOperation that adds the relevant data to Core Data. This operation is added to the queue.
On successful completion (using another block), (and after a specified delay) I call the method that started it all and return to step 1. Thus, i make another server call x seconds later.
This works great. I'm able to get data from the server and handle everything on the background. And because these are just NSOperations I'm able to put everything in the background, and perform multiple requests at a time. This works really well.
The ONLY problem that I currently have is that I'm unable to successfully cancel the operations once they are going.
I've tried something like the following :
- (void)flushQueue
{
self.isFlushingQueue = YES;
[self.operationQueue cancelAllOperations];
[self.operationQueue waitUntilAllOperationsAreFinished];
self.isFlushingQueue = NO;
NSLog(#"successfully flushed Queue");
}
where self.isFlushingQueue is a BOOL that I use to check before adding any new operations to the queue. This seems like it should work, but in fact it does not. Any ideas on stopping my Frankenstein creation?
Edit (Solved problem, but from a different perspective)
I'm still baffled about why exactly I was unable to cancel these operations (i'd be happy to keep trying possible solutions), but I had a moment of insight on how to solve this problem in a slightly different way. Instead of dealing at all with canceling operations, and waiting til queue is finished, I decided to just have a data structure (NSMutableDictionary) that had a list of all active connections. Something like this :
self.activeConnections = [NSMutableDictionary dictionaryWithDictionary:#{
#"UpdateContacts": #YES,
#"UpdateGroups" : #YES}];
And then before I add any operation to the queue, I simply ask if that particular call is On or Off. I've tested this, and I successfully have finite control over each individual server request that I want to be looping. To turn everything off I can just set all connections to #NO.
There are a couple downsides to this solution (Have to manually manage an additional data structure, and every operation has to start again to see if it's on or off before it terminates).
Edit -- In pursuit of a more accurate solution
I stripped out all code that isn't relevant (notice there is no error handling). I posted two methods. The first is an example of how the request NSOperation is created, and the second is the convenience method for generating the completion block.
Note the completion block generator is called by dozens of different requests similar to the first method.
- (void)updateContactsWithOptions:(NSDictionary*)options
{
//Hard coded for ease of understanding
NSString *contactsURL = #"api/url";
NSDictionary *params = #{#"sortBy" : #"LastName"};
NSMutableURLRequest *request = [self createRequestUsingURLString:contactsURL andParameters:params];
ConnectionCompleteBlock processBlock = [self blockForImportingDataToEntity:#"Contact"
usingSelector:#selector(updateContactsWithOptions:)
withOptions:options andParsingSelector:#selector(requestUsesRowsFromData:)];
BBYConnectionOperation *op = [[BBYConnectionOperation alloc] initWithURLRequest:request
andDelegate:self
andCompletionBlock:processBlock];
//This used to check using self.isFlushingQueue
if ([[self.activeConnections objectForKey:#"UpdateContacts"] isEqualToNumber:#YES]){
[self.operationQueue addOperation:op];
}
}
- (ConnectionCompleteBlock) blockForImportingDataToEntity:(NSString*)entityName usingSelector:(SEL)loopSelector withOptions:(NSDictionary*)options andParsingSelector:(SEL)parseSelector
{
return ^(BOOL success, NSData *connectionData, NSError *error){
//Pull out variables from options
BOOL doesLoop = [[options valueForKey:#"doesLoop"] boolValue];
NSTimeInterval timeInterval = [[options valueForKey:#"interval"] integerValue];
//Data processed before importing to core data
NSData *dataToImport = [self performSelector:parseSelector withObject:connectionData];
BBYImportToCoreDataOperation *importOperation = [[BBYImportToCoreDataOperation alloc] initWithData:dataToImport
andContext:self.managedObjectContext
andNameOfEntityToImport:entityName];
[importOperation setCompletionBlock:^ (BOOL success, NSError *error){
if(success){
NSLog(#"Import %#s was successful",entityName);
if(doesLoop == YES){
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:loopSelector withObject:options afterDelay:timeInterval];
});
}
}
}];
[self.operationQueue addOperation:importOperation];
};
}
Cancellation of an NSOperation is just a request, a flag that is set in NSOperation. It's up to your NSOperation subclass to actually action that request and cancel it's work. You then need to ensure you have set the correct flags for isExecuting and isFinished etc. You will also need to do this in a KVO compliant manner. Only once these flags are set is the operation finished.
There is an example in the documentation Concurrency Programming Guide -> Configuring Operations for Concurrent Execution. Although I understand that this example may not correctly account for all multi-threaded edge cases. Another more complex example is provided in the sample code LinkedImageFetcher : QRunLoopOperation
If you think you are responding to the cancellation request correctly then you really need to post your NSOperation subclass code to examine the problem any further.
Instead of using your own flag for when it is ok to add more operations, you could try the
- (void)setSuspended:(BOOL)suspend
method on NSOperationQueue? And before adding a new operation, check if the queue is suspended with isSuspended?

Resources