method not called from app delegate in ios

2019-06-11 06:29发布

问题:

I want to call a method from elsewhere in the app to get the user's location that was obtained in the app delegate. When calling CLLocation *getCurLoc = [AppDelegate getCurrentLocation]; from another view controller, nothing is returned.

The App Delegate is,

@synthesize locationManager;

CLLocation *location;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    locationManager = [[CLLocationManager alloc]init];
    locationManager.delegate = self;
    locationManager.desiredAccuracy=kCLLocationAccuracyKilometer;
    [locationManager startUpdatingLocation];

    return YES;
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{

    [locations lastObject];
    [manager stopUpdatingLocation];

    CLLocation *location =  [locations lastObject];
}

+(CLLocation *)getCurrentLocation{
    return location;
}

Changing it to an instance method with a "-" didn't work. Should "location" be made into an instance? Should the delegate be made into an instance, or is there a better way to access it from elsewhere?

回答1:

Martin's answer is an effective quick&dirty way of solving this problem in a way that is consistent with your approach - storing the location in appDelegate.

If you want to take a step further you might want to consider implementing a special object that would hold the data - the data model. It is considered a bad practice to store data in application delegate - it is not what it is there for (though it works perfectly fine in sample or small applications).

You could do something like this:

DataModel.h

#import <Foundation/Foundation.h>

@interface DataModel : NSObject

@property (strong) CLLocation *location;

+ (DataModel *)sharedModel;

@end

DataModel.m

#import "DataModel.h"

@class CLLocation;

@implementation DataModel

- (id) init
{
    self = [super init];
    if (self)
    {
        _location = nil;
    }
    return self;
}

+ (DataModel *)sharedModel
{
    static DataModel *_sharedModel = nil;
    static dispatch_once_t onceSecurePredicate;
    dispatch_once(&onceSecurePredicate,^
                  {
                      _sharedModel = [[self alloc] init];
                  });

    return _sharedModel;
}

@end

You would then need to #import "DataModel.h" wherever you need it. You would change your didUpdateLocations: to:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    [locations lastObject];
    [manager stopUpdatingLocation];

    [DataModel sharedInstance].location = [locations lastObject];
}

And from anywhere in the code you could get this location simply by [DataModel sharedInstance].location.

EDIT:

For a very simple app this approach might look as an overkill. But as soon as your app grows it surely pays off to use it.

This kind of class/object/singleton is ment to hold all the data your app needs (fixed and temporary). So all the data sources can make a good use of it. In short: it enables you to easily follow the model-view-controller guidelines.

You cold of course use another class/object/singleton to hold the temporary data - it depends on the complexity of your data-structure.

You don't have to specifically initialize this kind of object. It is initialized the first time you reference it. That is why dispatch_once is there for. It makes sure that there is one and only one instance of this shared object present: https://stackoverflow.com/a/9119089/653513

And this one single instance of [DataModel sharedInstance] will remain there until your app is terminated.

Apple uses similar approach for [NSUserDefaults standardDefaults], [UIApplication sharedApplicaton] for example.

I tend to put the #import "DataModel.h" into my projects Prefix.pch so I don't have to import it every single time I use it (it is used trough all the app).

PROS: - data accesible throughout the app - code reusability - MVC structure - code is more readable

CONS: - I couldn't really find one. Except that the dispatch_once(&onceSecurePredicate,^... might confuse one for the first couple of seconds.



回答2:

In didUpdateLocations, you define and set a local variable location, not the global variable location defined at the top of the file. Changing the line

CLLocation *location =  [locations lastObject];

to

location =  [locations lastObject];

would fix the problem. But the better solution is to use a property in the AppDelegate class. You can define it in a class extension:

@interface AppDelegate ()
@property(strong, nonatomic) CLLocation *location;
@end

Then you access it in instance methods like

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{

    [locations lastObject];
    [manager stopUpdatingLocation];
    self.location =  [locations lastObject];
}

and in a class method like

+(CLLocation *)getCurrentLocation{
    // Get the instance:
    AppDelegate *app = [[UIApplication sharedApplication] delegate];

    return app.location;
}

Update: If the AppDelegate is declared to conform so some protocol (in the public interface or in the class extension), for example:

@interface AppDelegate () <CLLocationManagerDelegate>
@property(strong, nonatomic) CLLocation *location;
@end

then the above code creates a warning

initializing 'AppDelegate *__strong' with an expression of incompatible type 'id<UIApplicationDelegate>'

and an explicit cast is necessary:

+(CLLocation *)getCurrentLocation{
    // Get the instance:
    AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    return app.location;
}


回答3:

You can access the AppDelegate from any point in your application using [UIApplication sharedApplication].delegate so you could do:

CLLocation *location = [(YourAppDelegateClassName*)[UIApplication sharedApplication].delegate getCurrentLocation];

The method getCurrentLocation needs to be an instance method (-(CLLocation *)getCurrentLocation). You will need also to import #import "YourAppDelegateClassName.h" in those files you need to use that method.

To avoid the casting and accessing [UIApplication sharedApplication].delegate everytime I prefer to implement a static method in my AppDelegates:

+ (YourAppDelegateClassName*)sharedDelegate {
    return (YourAppDelegateClassName*)[UIApplication sharedApplication].delegate;
}

So you can use any method like this:

CLLocation *location = [[YourAppDelegateClassName sharedDelegate] getCurrentLocation];