I am attempting to display objects stored in a NSArray on a map. The objects are being pulled from a Parse.com class called Affiliates after a method to fetch locations within 2 miles of a user has been executed. I have used NSLog to confirm that the query is executing correctly, so I know that the fetch is also being executed correctly.
When I try and run the app, I an error is thrown at the for(NSDictionary) section and the app freezes. The error states "Thread 1: EXC_BAD_ACCESS (access code 1, address 0x80120)" After consulting with Google I see that it is some type of memory allocation issue, but I have no idea why it is occurring or how to fix it.
#import "ViewController.h"
#import "MapAnnotation.h"
@import CoreLocation;
@interface ViewController () <CLLocationManagerDelegate>
@property (nonatomic,strong) NSArray *affiliates;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Ask for authorization to collect location from user
CLLocationManager * locationManager = [[CLLocationManager alloc] init];
// Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
if ([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
[locationManager requestWhenInUseAuthorization];
}
//Set map options
self.mapView.showsUserLocation = YES;
self.mapView.delegate = self;
self.mapView.scrollEnabled = YES;
self.mapView.zoomEnabled = YES;
self.mapView.userTrackingMode = YES;
// Store the user's location as a Parse PFGeoPoint and call the fetchAffiliatesNearPoint method
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError *error) {
if (!error) {
[self fetchAffiliatesNearPoint:geoPoint];
NSLog(@"Got User Location! %@", geoPoint);
}
}];
/* This is where I am having issues
for(NSDictionary *affiliates in affiliates) {
CLLocationCoordinate2D annotationCoordinate = CLLocationCoordinate2DMake([affiliates[@"latitude"] doubleValue], [affiliates[@"longitude"] doubleValue]);
MapAnnotation *annotation = [[MapAnnotation alloc] init];
annotation.coordinate = annotationCoordinate;
annotation.title = affiliates[@"name"];
annotation.subtitle = affiliates[@"url"];
[self.mapView addAnnotation:annotation];
}
*/
}
//Fetch an array of affiliates that are within two miles of the user's current location.
- (void)fetchAffiliatesNearPoint:(PFGeoPoint *)geoPoint
{
PFQuery *query = [PFQuery queryWithClassName:@"Affiliates"];
[query whereKey:@"geometry" nearGeoPoint:geoPoint withinMiles:2.0];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error)
{
self.affiliates = objects;
NSLog(@"Nearby Locations %@", _affiliates);
}
}];
}
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
//Zoom map to users current location
if (!self.initialLocation) {
self.initialLocation = userLocation.location;
MKCoordinateRegion mapRegion;
mapRegion.center = mapView.userLocation.coordinate;
mapRegion.span.latitudeDelta = .025;
mapRegion.span.longitudeDelta = .025;
[mapView setRegion:mapRegion animated: YES];
}
}
@end
I haven't worked with Parse either but there are a couple of obvious issues that it may be helpful to point out:
This line doesn't make sense:
for(NSDictionary *affiliates in affiliates)
Here, the affiliates
after in
refers to the locally declared dictionary variable and not to the property variable of the same name. Since the local variable has no initialized reference and is pointing to some random memory, you get the EXC_BAD_ACCESS
. Use self.affiliates
to refer explicitly to the property.
Additionally, it's extremely confusing to name the looping variable the same as the array it is looping through. You are looping through an array of "affiliates" (plural). The array contains dictionaries and each dictionary is an "affiliate" (singular). It would be less confusing to name the dictionary variable as affiliate
(singular). Example:
for (NSDictionary *affiliate in self.affiliates)
Also change the remaining references inside the loop from affiliates
to affiliate
.
Currently, the loop on self.affiliates
is done immediately after geoPointForCurrentLocationInBackground
is started. The geoPointForCurrentLocationInBackground
appears to be asynchronous which means the loop on self.affiliates
will execute before geoPointForCurrentLocationInBackground
has actually set that property.
Instead of looping through self.affiliates
in viewDidLoad
right after starting geoPointForCurrentLocationInBackground
, move the loop to inside the completion block of findObjectsInBackgroundWithBlock
(after self.affiliates
is set). Example:
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error)
{
self.affiliates = objects;
NSLog(@"Nearby Locations %@", _affiliates);
for(NSDictionary *affiliate in self.affiliates) {
...
}
}
}];