Using the AppDelegate to share data

2019-02-13 10:31发布

问题:

I've found a couple of sources that explain how to use the AppDelegate to share data between objects in an iOS application. I've implemented it quite painlessly and it looks like a good approach in my situation. Thinking about what could be done using the AppDelegate, I am wondering where the line should be drawn.

Obviously there are other ways to share data across view controllers, use Singleton objects, and NSUserDefaults. When is it appropriate to use the AppDelegate to share data? In my current situation, I use this approach to store the appleDeviceToken used for push notifications. I use that token when users login or logout of the app.


In MyAppDelegate.h I declare the property:

@property (nonatomic, retain) NSString *appleDeviceToken;

In MyAppDelegate.m I synthesize appleDeviceToken and then set it:

@synthesize appleDeviceToken;    

------------------------------------------------------

- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
  NSString *devToken = [[[[deviceToken description]
                          stringByReplacingOccurrencesOfString:@"<"withString:@""]
                         stringByReplacingOccurrencesOfString:@">" withString:@""]
                        stringByReplacingOccurrencesOfString: @" " withString: @""];
  appleDeviceToken = devToken;
}

Then, in my LoginViewController.m I retrieve it and post it to my server:

  NSString *urlForDeviceTokenPost = [NSString stringWithFormat: @"/api/users/%i/appleDeviceToken", userId];

  MyAppDelegate *appDelegate = (MyAppDelegate*) [UIApplication sharedApplication].delegate;
  NSString *appleDeviceTokenStr = appDelegate.appleDeviceToken;

  AppleDeviceToken *appleDeviceToken = [[AppleDeviceToken alloc] init];
  appleDeviceToken.deviceToken = appleDeviceTokenStr;

  [[RKObjectManager sharedManager] postObject:appleDeviceToken delegate:self];

This works great so far, but is this the ideal approach? What else should I know?

回答1:

When the data and objects are truly global and/or cannot be pushed further down the graph. Storage at this high level is usually not required. As well, your implementations should usually have little to no knowledge about the app delegate -- What's worse than a Singleton? The God-Singleton :) If the app delegate is complex, something's gone wrong. If the app delegate's interface is visible (via #import) to many of your implementations or they message it directly, then something is wrong.

There is no need for an (idiomatic ObjC) singleton -- there is one instance of the app delegate.

NSUserDefaults is for persistence of small values (as the name implies) -- the ability to share is a side effect.

Since the data is already sent into the app delegate by UIKit in this case, that may be a fine place to store the data, or an object representation of. You might also consider forwarding those messages to an appropriate handler. The important point -- In most cases, you would want initialization to flow down the object graph, and to flow from the lowest points possible (e.g. as opposed to many objects referring back to the app delegate). So you might see the app delegate set a top-level view controller's model, but the view controller can then set the models of the view controllers it pushes. This way you will reduce dependencies and control flow, cause and effect will be easier to trace, and you will be able to test it more easily -- free of the context of a massive global state.



回答2:

The following line always indicates that you've done something wrong:

MyAppDelegate *appDelegate = (MyAppDelegate*) [UIApplication sharedApplication].delegate;

The application delegate is the delegate of the UIApplication. It is called that for a reason. It is not called the ApplicationDataStore or even the ApplicationCoordinator. The fact that you're asking the application for its delegate and then treating it as something other than id<UIApplicationDelegate> means that you've asked it to do something it isn't tasked with doing. It's tasked with managing the things UIApplication needs (which doesn't mean "everything the 'app' needs).

It appears that you've already built a place to store this information: RKObjectManager. I would have the app delegate pass the token there, and I'd have the login view controller just note that it is time to push it. I wouldn't even put the string @"/api/users/%i/appleDeviceToken" in the view controller. That's not related to displaying the view. That's something for you networking stack (which you seem to have housed in RKObjectManager). "ViewController" means "controller for helping display the view" not "processor of the operation that the view represents."



回答3:

That seems like an appropriate use. The application delegate is tempting to misuse because it's an easily-accessible object that's already present in every app. It has a real purpose, though, which is, as its title indicates, to make decisions for the application object, just as a table view delegate does for its table view object.

In this case, you're storing information that was passed to the delegate from the application itself. I'd say that's where the line is drawn.

Storing this token seems to be in accordance with the app delegate's purpose, unless you had another controller object which was focussed on dealing with remote notifications. In that case, you would probably just pass the token right on to that controller.



回答4:

I'm more pragmatic. Since the appDelegate in my app knows how the tabBarController was populated, and all the navigation controllers, I have several methods there that let some arbitrary class communicate with some other class - usually these are single instances of some class (but not singletons). That said, if what you want to put there does not have a compelling reason to be in the appDelegate, well, it probably doesn't belong there!