Delegate method won't fire (is null). Using ARC - ios

I'm having trouble getting my delegate method to fire. I have the following code:
#class Location;
#protocol LocationDelegate
- (void)location:(Location*)location foundLocation:(CLLocation*)location;
- (void)location:(Location*)location failedToLocateUserWithError:(NSError*)error;
#end
#interface Location : NSObject {
__unsafe_unretained id <LocationDelegate> delegate;
}
#property (nonatomic, assign) id <LocationDelegate> delegate;
#end
...
#implementation Location
#synthesize delegate;
- (void)startUpdatingLocation {
NSLog(#"%#", delegate); // prints '(null)'
...
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSLog(#"%#", delegate); // prints '(null)'
[delegate location:self foundLocation:newLocation];
}
#end
Used to work fine for other projects, so wondering if it has to do with ARC? The object that allocates Location does conform to LocationDelegate. I am also setting the delegate to self. But the delegate method just isn't firing, and if I output it, it's null.
Thanks

From just the code you've pasted here, I don't see anything that is holding on to your delegate (a retained reference).
Everything you've pasted specifically shows you going out of your way to not have the delegate retained by this code.
If thats how you want it to be, then you better be sure it's been retained elsewhere - otherwise ARC will correctly conclude that since no one has a strong (retained) reference to the delegate, that it's safe (and proper) to release it.
The object that allocates Location does conform to LocationDelegate. I
am also setting the delegate to self.
And who has a strong reference to that object?

Wherever you are allocating Location have you assigned the delegate?
Eg:
Location *location = [[Location alloc] init];
location.delegate = self;

Related

Using a class (not an instance) as an iOS delegate, CLLocationManager delegate callbacks aren't called

Related: Can I use a class method as a delegate callback?
I'm trying to use a static class, MyClass, as a delegate for its own static CLLocationManager member, but the CLLocationManager delegate methods I've implemented aren't being called. I've set the delegate as [myClass class], properly implemented the delegate methods, and included the protocol in MyClass.h.
MyClass.h
#interface iOSSonic : NSObject <CLLocationManagerDelegate>
MyClass.m
locationManager declaration:
#implementation myClasss : NSObject
...
static CLLocationManager *locationManager = nil;
I'm lazily instantiating the static CLLocationManager via the follow method:
+(CLLocationManager*)getLocationManager {
if (locationManager == nil) {
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = [myClass class]; // we set the delegate of locationManager to self.
locationManager.desiredAccuracy = kCLLocationAccuracyBest; // setting the accuracy
locationManager.distanceFilter = 0.5; // get updates for location changes > 0.5 m
[locationManager requestWhenInUseAuthorization];
}
return locationManager;
}
...and then from my ViewController calling the following MyClass method:
+(void)myFunction {
[self.getLocationManager startUpdatingLocation];
}
Delegate method implementations:
...
+(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
...
}
+(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
...
}
ViewController.m
// no initialization needed for static myClass
- (IBAction)onButtonClick:(id)sender {
[myClass myFunc] // This should trigger the didUpdateLocations delegate method, but it doesn't
To ensure that this wasn't some problem related to having the delegate be a static (non-instantiable) class and the delegate callbacks be class methods, I also tried with locationManager as a #property rather than a static member, and created an instance of myClass, setting myClass's locationManager's delegate to self. I also replaced getLocationManager with an overridden locationManager getter, and changed the delegate callbacks to instance methods.
MyClass.m
Initialization:
-(id)init {
if (self = [super init]) {
// do nothing
}
return self;
}
LocationManager declaration and instantiation:
...
#interface MyClass()
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
#implementation
...
// Lazily instantiate locationManager
-(CLLocationManager*)locationManager {
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self; // we set the delegate of locationManager to self.
_locationManager.desiredAccuracy = kCLLocationAccuracyBest; // setting the accuracy
_locationManager.distanceFilter = 0.5; // get updates for location changes > 0.5 m
[_locationManager requestWhenInUseAuthorization];
}
return _locationManager;
}
Delegate method implementations:
...
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
...
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
...
}
ViewContoller.h
...
#property (strong, nonatomic) myClass *myClassInstance;
...
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
...
self.myClassInstance = [[myClass alloc] init];
What am I doing wrong?
It was a stupid location services permissions thing. Turns out it had nothing to do with static members, classes vs. instances, etc. This fixed it for me: https://stackoverflow.com/a/25765345/1402368
Of course it was something stupid like this...
Here's my understanding of what's going on:
Instance method calls and class method calls are semantically different in Objective-C, and not interchangeable.
The method declarations:
+(void)someMethod;
and
-(void)someMethod;
Define 2 different kinds of methods. To call them, you have to know if you're calling an instance method or a class method, and code accordingly.
The location manager is written to call INSTANCE methods on it's delegate, not class methods.
Thus, you can't do what you're trying to do (make a CLASS a delegate instead of an instance of a class.)
You might be able to design your own custom class who's objects expect to have a class set as their delegate, but then you would only ever be able to assign a class as the delegate, never an instance of that class.

Need to access NSArray object that contains CLLocation information

I am new to objective-c and have been trying to work on this problem for the past 2 and a half hours. So far I was able to have some success and my app can start searching for the user's location as soon as the app is launched.
I then setup a delegate for CLLocationManager based off of this advice:
"Add the syntax to declare that this class (the ViewController.h/m) is adopting this particular protocol. By default, any other classes that create an instance of this class will also automatically adopt the protocol.
#interface ViewController : UIViewController <CLLocationManagerDelegate>
The line of code listed above shows the syntax used to show ViewController adopts the CLLocationManagerDelegate protocol. We simply add it between angled brackets <> in the #interface line in the header."
So I successfully added the above line inside my ViewController.m and ViewController.h file, and then I also followed that same tutorials advice of:
"The full method is:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
and when implemented it tells the delegate that new location data is available. We have two arguments on this method. The first lets you know which CLLocationManager provided the update and the last provides the CLLocation information which is stored in an NSArray."
Below is all of my ViewController.h code and then next wll be my ViewController.m code:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface ViewController : UIViewController <CLLocationManagerDelegate>
#property (nonatomic, strong) IBOutlet UILabel *gpsLabel;
-(IBAction)gpsButton;
#end
Here is my ViewController.m code:
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface ViewController () <CLLocationManagerDelegate>
#property (nonatomic, strong) CLLocationManager * gpsLM;
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.gpsLM = [[CLLocationManager alloc]init];
[self.gpsLM startUpdatingLocation];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)gpsButton{
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
}
#end
I am confused on where to go from here. Assuming I am even doing everything correctly so far(am I?), then how do I access the location data that has been stored in the NSArray object called locations?
Thank you for the help.
Welcome to the iOS community!
First, you'll probably find it helpful to look at the CLLocationManagerDelegate reference, under the heading for the locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations method. The important bit here is:
locations: An array of CLLocation objects containing the location data.
This array always contains at least one object representing the
current location. If updates were deferred or if multiple locations
arrived before they could be delivered, the array may contain
additional entries. The objects in the array are organized in the
order in which they occurred. Therefore, the most recent location
update is at the end of the array.
The important thing to get is that this method is called by the system when the user's location changes.
So, for instance, you could print out a message to your console when the location updates by changing your implementation to something like:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *currentLocation = [locations lastObject];
NSLog(#"Location is: %.5f %.5f", currentLocation.coordinate.latitude, currentLocation.coordinate.longitude);
}
If you instead want to do something in response to user activity, you could use manager.location, which is automatically updated every time the CLLocationManager detects a new location. (If this didn't exist, you would need to add another instance variable to your class to store the most recent location, and update that in locationManager:didUpdateLocations:.)
So, for instance, if you wanted to update your label with the current location whenever the button was pressed, you could add something like:
-(IBAction)gpsButton {
CLLocation *currentLocation = self.gpsLM.location;
self.gpsLabel.text = [NSString stringWithFormat:#"Location is: %.5f, %.5f"];
}
(Note: this assumes that the gpsButton action and the gpsLabel outlet are hooked up to something graphically in Interface Builder.)
If you're familiar with other programming languages, this is a push versus pull distinction. CLLocationManager provides a push model (it calls your locationManager:didUpdateLocations: method to inform you immediately of changes), and also a pull model (you can ask it for the most current location through .location at any time). Which one you use will depend on what you want to do.

NSLog of CLLocation does not print at launch, only when button is pressed

My app display the user's last known/current coordinates in a text label when a button is pressed.
I set up a block of code in my main file to print the user's latitude coordinate to the log, but it does not print anything to the log when I run the app.
Why won't the NSLog print to the console?
Here is the snippet of code that is supposed to be printing the location to the log once the app launches and the user allows the app to access their location:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
CLLocation * currentLocation = [locations lastObject];
NSLog(#"%f", currentLocation.coordinate.latitude);
Below is my full ViewController.h code:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface ViewController : UIViewController <CLLocationManagerDelegate>
#property (nonatomic, strong) IBOutlet UILabel * gpsLabel;
#property (nonatomic, strong) CLLocationManager * gpsLM;
-(IBAction)gpsButton;
#end
And here is my full ViewController.m code:
#import "ViewController.h" //This imports the all of the code we have typed in the ViewController.h file.
#import <CoreLocation/CoreLocation.h> //This imports the CoreLocation framework needed for location apps.
//This assigns the Location Manager's delegate to this view controller
#interface ViewController () <CLLocationManagerDelegate>
//This tells the delegate that new location data is available. Manager is the object that updates the event, and the locations object is where the array of location data is stored.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//This allocates memory for and initializes the gpsLM object we setup in ViewController.h
//This means that we can now use the object and do things with it.
self.gpsLM = [[CLLocationManager alloc]init];
//This calls a startUpdatingLocation method for our CLLocationManager object called gpsLM.
//Because this is all in viewDidLoad, it all gets executed right away as soon as the app is opened.
[self.gpsLM startUpdatingLocation];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//This executes the instance method that we declared above in the header.
//Now we are actually implementing the method and can tell it what we want it to do.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
//This creates an object called currentLocation and sets it's value to whatever the last value is in the locations array.
//Notice how it is also calling a method of lastObject for the object called locations.
//So remember that you can set variables and objects equal to the result of a method call.
CLLocation * currentLocation = [locations lastObject];
//This prints out text to the debug console that states the latitude coordinate of the user's iPhone.
NSLog(#"%f", currentLocation.coordinate.latitude);
}
-(IBAction)gpsButton{
CLLocation * currentLocation = self.gpsLM.location;
self.gpsLabel.text = [NSString stringWithFormat:#"Your Location is %#", currentLocation];
}
#end
It seems that you forgot to assign the location manager delegate:
self.gpsLM = [[CLLocationManager alloc]init];
self.gpsLM.delegate = self; // <-- ADD THIS
[self.gpsLM startUpdatingLocation];
Without this assignment, the location manager doesn't know what object to give the location update to. The method locationManager:didUpdateLocations: never runs.

CLLocationManager is alive even when not needed

I have a mapping functionality in my app. It belongs to one of the Tab Bar controller tabs.
The problem I have is that the app is asking for location permissions right after launch.
I dont want to bother the user with location related questions until he actually goes to that map part of the app.
If I understand properly, location manager should not bother to ask for that until I instantiate it. Also, as soon as the user leaves that map part of the app, either by selecting other tabs or by pressing home button..I nil the location manager.
Why is it then asking for that so soon? My question is whether there is some special rule in this particular case...or it is caused by my flawed app design?
I'd go with the "flowed app design" one. You don't showed any code, so it's hard to say, but UITabBarController instantiates all its View Controllers at once, so your CCLocation class is probably being initialized with your TabBar.
What you could do is: initialize your CCLocation stuff only on the -viewWillAppear method on the View Controller that you actually use it.
You can write something like this.
h:
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#interface LocationController : NSObject <CLLocationManagerDelegate> {
CLLocationManager *locationManager;
CLLocation *currentLocation;
}
+ (LocationController *)sharedInstance;
- (void)start;
- (void)stop;
#property (nonatomic, strong) CLLocation *currentLocation;
#end
m:
#import "LocationController.h"
#implementation LocationController
static LocationController *sharedInstance;
+ (LocationController *)sharedInstance {
static LocationController *sharedClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClient = [[LocationController alloc] init];
});
return sharedClient;
}
-(id) init {
if (self = [super init]) {
currentLocation = [[CLLocation alloc] init];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[self start];
}
return self;
}
- (void)start {
[locationManager startUpdatingLocation];
}
- (void)stop {
[locationManager stopUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *loc = [locations lastObject];
if ( abs([loc.timestamp timeIntervalSinceDate: [NSDate date]]) < 120) {
self.currentLocation = loc;
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"%#", error);
}
#end
Then whenever you need to locate you can do the following:
[[LocationController sharedInstance] start];
Where are you instantiating the CLLocationManager? While all the view controllers are instantiated when the tab bar controller is instantiated, their respective views are not created until you go to the relevant tab. So, if you're instantiating your CLLocationManager in one of the controller's init methods, then that location manager will be created when the controllers are (i.e. when the tab bar controller is instantiated). If you have the instantiation of the location manager in viewDidLoad, though, it shouldn't be instantiated until you click on the appropriate tab.

Access a assign delegate property from Objective-C Block? (iOS)

Is it ok to access a delegate property from an Block?
#interface TheObject : NSObject
...
#property (nonatomic, assign) id<SomeDelegate> delegate;
#synthesize delegate
- (void) someMethod {
[someObject doSomethingWithCompletionHandler:^(NSArray *)someArray {
[self.delegate otherMethod:someArray];
}];
}
What happens if the delegate is nilled (from the dealloc method in the object that has also set the delegate) before the completion handler is called?
Could it be a memory bug?
I don't know how to use __block for properties...
Answer from below:
If the delegate is nilled from the object which is the delegate on the dealloc call, everything is fine.
#property (nonatomic, retain) TheObject theObject;
#synthezise theObject = _theObject;
- (void) thatMethod {
self.theObject = [[TheObject alloc] init] autorelease];
_theObject.delegate = self;
}
- (void) dealloc {
_theObject.delegate = nil;
self.theObject = nil;
}
Normally, if your delegate is deallocated before the block is executed, then it would access garbage, since the block is an assign property and the block retains self rather than the delegate since you access it by reference.
However, since you've set it up so that self.delegate gets nilled if delegate is deallocated, you won't have that problem. Instead, if your delegate were deallocated, then in your code you'd just be sending the otherMethod: method to nil, which would do nothing, but also cause no errors.
If you want the method to definitely be sent to your delegate, the solution is to access it by value instead of reference:
- (void)someMethod {
id <SomeDelegate> delegateForBlock = self.delegate;
[someObject doSomethingWithCompletionHandler:^(NSArray *)someArray {
[delegateForBlock otherMethod:someArray];
}];
}
That way delegateForBlock will be a pointer to the same object as self.delegate (at the time you execute someMethod:), and it will be retained.
To find out more about how this works, check out Blocks Programming Topics.
If the delegate is deallocated, you will be accessing a garbage value which will result in EXC_BAD_ACCESS.
You could either do
id <SomeDelegate> dlg = self.delegate
[someObject doSomethingWithCompletionHandler:^(NSArray *)someArray {
[dlg otherMethod:someArray];
}];
or access the ivar directly, so that the block retains it
[someObject doSomethingWithCompletionHandler:^(NSArray *)someArray {
[delegate otherMethod:someArray];
}];

Resources