问题
I am using the below code to fetch location information via Geocoding and then add a map pin to a Google Map View. The code uses a For loop to cycle through each place in my database. The problem is that the code fails to return location info for about 50% of the places when run. These failed items are saved in the failedLoad array as per the code below.
Can anyone suggest why this is ? Also since these failed items are saved in the "failedLoad" array, is there a way I could use this array to load any missing pins ?
EDIT
The failed items are due to a 620 error which means that I am submitting items too quickly. How can I add a delay into the code ?
Thanks !
-(void)displayPlaces {
for (PlaceObject *info in mapLocations) {
// GET ANNOTATION INFOS
NSString * addressOne = info.addressOne;
NSString * name = info.name;
NSString * postCode = info.postCode;
NSString * addressTwo = [addressOne stringByAppendingString:@",London,"];
NSString * address = [addressTwo stringByAppendingString:postCode];
NSString* urlString = [NSString stringWithFormat:@"http://maps.google.com/maps/geo?q=%@&output=csv", [address stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURL* url = [NSURL URLWithString:urlString];
NSURLRequest* req = [NSURLRequest requestWithURL:url];
OHURLLoader* loader = [OHURLLoader URLLoaderWithRequest:req];
[loader startRequestWithCompletion:^(NSData* receivedData, NSInteger httpStatusCode) {
NSString* locationString = loader.receivedString;
NSArray *listItems = [locationString componentsSeparatedByString:@","];
double latitude = 0.0;
double longitude = 0.0;
if([listItems count] >= 4 && [[listItems objectAtIndex:0] isEqualToString:@"200"]) {
latitude = [[listItems objectAtIndex:2] doubleValue];
longitude = [[listItems objectAtIndex:3] doubleValue];
}
else {
NSLog(@"Error %@",name);
[failedLoad addObject : info];
}
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude;
coordinate.longitude = longitude;
MyLocation *annotation = [[[MyLocation alloc] initWithName:name address:address coordinate:coordinate] autorelease];
[mapViewLink addAnnotation:annotation];
} errorHandler:^(NSError *error) {
NSLog(@"Error while downloading %@: %@",url,error);
}];
}
}
回答1:
Instead of using a for loop and send all your requests at the same time, you probably should send them one after the other (or 5 by 5?)
Here is one way to do it (not tested in real code, just typed as I go so may have some typo):
// In the instance variables, have:
@property(retain) NSMutableSet* mapLocationsToGeocode;
// When you want to decode, use:
self.mapLocationsToGeocode = [NSMutableSet setWitharray:mapLocations];
// (Or add to the existing NSSet if you have one and add Places using multple passes)
[self popLocationAndGeocode];
-(void)popLocationAndGeocode
{
// Pop any location from the set
PlaceObject* onePlace = [mapLocationsToGeocode anyObject];
// Build the URL given the PlaceObject
NSString* address = [NSString stringWithFormat:@"%@,London,%@",info.addressOne,info.postCode];
NSString* name = info.name;
NSString* urlString = [NSString stringWithFormat:@"http://maps.google.com/maps/geo?q=%@&output=csv", [address stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest* req = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
// Remove it so it won't be poped again
[mapLocationsToGeocode removeObject:onePlace];
// Send the request here to decode the PlaceObject
OHURLLoader* loader = [OHURLLoader URLLoaderWithRequest:req];
[loader startRequestWithCompletion:^(NSData* receivedData, NSInteger httpStatusCode) {
NSString* locationString = loader.receivedString;
NSArray* listItems = [locationString componentsSeparatedByString:@","];
...
if([listItems count] >= 4 && [[listItems objectAtIndex:0] isEqualToString:@"200"]) {
// Process with latitude and longitude, add your MKAnnotation, etc
} else {
NSLog(@"Error %@",name);
[failedPlaces addObject:onePlace];
}
...
// Schedule the next decoding request (1)
if ([mapLocationsToGeocode count]) [self performSelector:@selector(popLocationAndGeocode) withObject:nil afterDelay:0.1];
} errorHandler:^(NSError *error) {
NSLog(@"Error while downloading %@: %@",url,error);
[failedPlaces addObject:onePlace];
// Schedule the next decoding request anyway (1)
if ([mapLocationsToGeocode count]) [self performSelector:@selector(popLocationAndGeocode) withObject:nil afterDelay:0.1];
}];
// Schedule the next decoding request (2) -- solution 2
// if ([mapLocationsToGeocode count]) [self performSelector:@selector(popLocationAndGeocode) withObject:nil afterDelay:1.0]; // wait one sec before sending next request
}
Of course, dont forget to set the property back to nil when done (or in the dealloc
) to release the memory.
In case (1), I call performSelector:withObject:afterDelay
in both the completion and error blocks, so that the next request/decoding process is called only once the first has finished. This way your requests are somewhat serialized.
In case (2) (commented/disabled), I call performSelector:withObject:afterDelay
right after the startRequestWithCompletion:...
method, so it won't wait for the end of the first request to pop the next one. But you will wait (hopefully) long enough so that you won't reach the GoogleAPI rate limit
Note that this is not the only solution, there are plenty other possibilities. One being to use NSOperationQueue
to queue the requests one after the other on a queue and add dependencies to it, or schedule the sending process of the requests on a GCD serial queue (yeah, I know I told you not to use GCD to actually send your requests, but that still apply, still dont use GCD+synchronous requests, but you can use GCD to queue blocks that will call [OHURLLoader startRequestWithCompletion:...]
on the main thread one after the other; the request itself still being executed on the main thread by the RunLoop)
来源:https://stackoverflow.com/questions/7634246/for-loop-with-google-geocoding-high-error-rate-iphone