问题
I was trying to call an existing method of my ViewController.m
from AppDelegate.m
inside the applicationDidEnterBackground
method, so I found this link: Calling UIViewController method from app delegate, which told me to implement this code:
In my ViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.myViewController = self;
}
In my AppDelegate:
@class MyViewController;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (weak, nonatomic) MyViewController *myViewController;
@end
And in the AppDelegate's implementation:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self.myViewController method];
}
So I put this code in my project and it worked fine, but I didn't understand how the code works, line by line. What does the sharedApplication
do? Why must I set a delegate instead of just creating an instance of ViewController, like:
ViewController * instance = [[ViewController alloc] init];
[instance method];
回答1:
Background information (class definition vs class instance)
The important concept here is the difference between a class definition and a class instance.
The class definition is the source code for the class. For example ViewController.m
contains the definition for the myViewController
class, and AppDelegate.m
contains the definition for the AppDelegate
class. The other class mentioned in your question is UIApplication
. That is a system-defined class, i.e. you don't have the source code for that class.
A class instance is a chunk of memory on the heap, and a pointer to that memory. A class instance is typically created with a line of code like this
myClass *foo = [[myClass alloc] init];
Note that alloc
reserves space on the heap for the class, and then init
sets the initial values for the variables/properties of the class. A pointer to the instance is then stored in foo
.
When your application starts, the following sequence of events occurs (roughly speaking):
- the system creates an instance of the UIApplication class
- the pointer to the UIApplication instance is stored somewhere in a system variable
- the system creates an instance of the AppDelegate class
- the pointer to the AppDelegate is stored in a variable called
delegate
in the UIApplication instance - the system creates an instance of the MyViewController class
- the pointer to the MyViewController class is stored somewhere
The storage of the pointer to MyViewController is where things get messy. The AppDelegate class has a UIWindow property called window
. (You can see that in AppDelegate.h.) If the app only has one view controller, then the pointer to that view controller is stored in the window.rootViewController
property. But if the app has multiple view controllers (under a UINavigationController or a UITabBarController) then things get complicated.
The spaghetti code solution
So the issue that you face is this: when the system calls the applicationDidEnterBackground
method, how do you get the pointer to the view controller? Well, technically, the app delegate has a pointer to the view controller somewhere under the window
property, but there's no easy way to get that pointer (assuming the app has more than one view controller).
The other thread suggested a spaghetti code approach to the problem. (Note that the spaghetti code approach was suggested only because the OP in that other thread didn't want to do things correctly with notifications.) Here's how the spaghetti code works
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.myViewController = self;
This code retrieves the pointer to the UIApplication instance that the system created, and then queries the delegate
property to get a pointer to the AppDelegate instance. The pointer to self
, which is a pointer to the MyViewController instance, is then stored in a property in the AppDelegate.
The pointer to the MyViewController instance can then be used when the system calls applicationDidEnterBackground
.
The correct solution
The correct solution is to use notifications (as in kkumpavat's answer)
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)didEnterBackground
{
NSLog( @"Entering background now" );
}
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
With notifications, you aren't storing redundant pointers to your view controllers, and you don't have to figure out where the system has stored the pointer to your view controller. By calling addObserver
for the UIApplicationDidEnterBackgroundNotification
you're telling the system to call the view controller's didEnterBackground
method directly.
回答2:
You have two question here.
1) What does the sharedApplication
do?
The [UIApplication sharedApplication]
gives you UIApplication instance belongs to your application. This is centralised point of control for you App. For more information you can read UIApplication class reference on iOS developer site.
2) Why must I set a delegate instead of just creating an instance of ViewController?
Creating controller in AppDelegate
again using alloc
/init
will create new instance and this new instance does not point to the controller you are referring to. So you will not get result you are looking for.
However in this particular use case of applicationDidEnterBackground
, you don't need to have reference of you controller in AppDelegate
. You ViewController can register for UIApplicationDidEnterBackgroundNotification
notification in viewDidLoad
function and unregister in dealloc
function.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yourMethod) name:UIApplicationDidEnterBackgroundNotification object:nil];
//Your implementation
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
回答3:
The view controller is already instantiated as part of the NIB/storyboard process, so if your app delegate does its own alloc
/init
, you are simply creating another instance (which bears no relation to the one created the NIB/storyboard).
The purpose of the construct you outline is merely to give the app delegate a reference to the view controller that the NIB/storyboard instantiated for you.
来源:https://stackoverflow.com/questions/23444870/call-a-method-from-appdelegate-objective-c