applicationDidEnterBackground
メソッド内のViewController.m
からAppDelegate.m
の既存のメソッドを呼び出そうとしたので、次のリンクを見つけました: アプリデリゲートからUIViewControllerメソッドを呼び出す 、このコードを実装するように指示されました:
私のViewController.mで
-(void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.myViewController = self;
}
私のAppDelegateで:
@class MyViewController;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (weak, nonatomic) MyViewController *myViewController;
@end
そして、AppDelegateの実装では:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self.myViewController method];
}
そのため、このコードをプロジェクトに入れて正常に機能しましたが、コードがどのように機能するかを1行ずつ理解できませんでした。 sharedApplication
は何をしますか?次のように、ViewControllerのインスタンスを作成するだけでなく、デリゲートを設定する必要があるのはなぜですか。
ViewController * instance = [[ViewController alloc] init];
[instance method];
背景情報(クラス定義とクラスインスタンス)
ここで重要な概念は、クラス定義とクラスインスタンスの違いです。
クラス定義は、クラスのソースコードです。たとえば、ViewController.m
にはmyViewController
クラスの定義が含まれ、AppDelegate.m
にはAppDelegate
クラスの定義が含まれます。あなたの質問で言及されている他のクラスはUIApplication
です。これはシステム定義のクラスです。つまり、そのクラスのソースコードがありません。
クラスインスタンスは、ヒープ上のメモリのチャンクであり、そのメモリへのポインタです。クラスインスタンスは通常、次のようなコード行で作成されます
myClass *foo = [[myClass alloc] init];
alloc
はクラスのヒープ上にスペースを予約し、次にinit
はクラスの変数/プロパティの初期値を設定することに注意してください。インスタンスへのポインタは、foo
に格納されます。
アプリケーションが起動すると、次の一連のイベントが発生します(大まかに言えば)。
delegate
という変数に格納されます。MyViewControllerへのポインタのストレージは、物事が厄介になる場所です。 AppDelegateクラスには、window
というUIWindowプロパティがあります。 (AppDelegate.hで確認できます。)アプリにView Controllerが1つしかない場合、そのViewControllerへのポインターはwindow.rootViewController
プロパティに格納されます。ただし、アプリに複数のビューコントローラー(UINavigationControllerまたはUITabBarControllerの下)がある場合、状況は複雑になります。
スパゲッティコードソリューション
したがって、直面する問題はこれです。システムがapplicationDidEnterBackground
メソッドを呼び出すとき、どのようにしてViewControllerへのポインタを取得しますか?技術的には、アプリデリゲートにはwindow
プロパティの下のどこかにビューコントローラーへのポインターがありますが、そのポインターを取得する簡単な方法はありません(アプリに複数のビューコントローラーがあると仮定します)。
他のスレッドは、問題へのスパゲッティコードアプローチを提案しました。 (スパゲッティコードのアプローチが提案されたのは、他のスレッドのOPが通知で正しく処理することを望まなかったためです。)スパゲッティコードのしくみは次のとおりです。
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.myViewController = self;
このコードは、システムが作成したUIApplicationインスタンスへのポインターを取得し、delegate
プロパティを照会して、AppDelegateインスタンスへのポインターを取得します。 MyViewControllerインスタンスへのポインタであるself
へのポインタは、AppDelegateのプロパティに格納されます。
MyViewControllerインスタンスへのポインタは、システムがapplicationDidEnterBackground
を呼び出すときに使用できます。
正しい解決策
正しい解決策は、通知を使用することです(kkumpavatの回答のように)
- (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];
}
通知を使用すると、View Controllerへの冗長なポインターを保存する必要がなく、システムがViewControllerへのポインターをどこに保存したかを把握する必要もありません。 addObserver
に対してUIApplicationDidEnterBackgroundNotification
を呼び出すことにより、ViewControllerのdidEnterBackground
メソッドを直接呼び出すようにシステムに指示します。
ここに2つの質問があります。
1)sharedApplication
は何をしますか?[UIApplication sharedApplication]
は、UIApplicationインスタンスがアプリケーションに属していることを示します。これは、アプリの集中管理ポイントです。詳細については、iOS開発者サイトの IApplicationクラスリファレンス を参照してください。
2)ViewControllerのインスタンスを作成するだけでなく、デリゲートを設定する必要があるのはなぜですか?
再びAppDelegate
/alloc
を使用してinit
にコントローラーを作成すると、新しいインスタンスが作成されます。この新しいインスタンスは、参照しているコントローラーを指していません。だからあなたはあなたが探している結果を得ることができません。
ただし、applicationDidEnterBackground
のこの特定のユースケースでは、AppDelegate
にコントローラーの参照を含める必要はありません。 ViewControllerは、UIApplicationDidEnterBackgroundNotification
関数でviewDidLoad
通知に登録し、dealloc
関数で登録を解除できます。
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yourMethod) name:UIApplicationDidEnterBackgroundNotification object:nil];
//Your implementation
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
ビューコントローラは、NIB /ストーリーボードプロセスの一部としてすでにインスタンス化されているため、アプリデリゲートが独自のalloc
/init
を実行する場合は、単に別のインスタンスを作成しているだけです(これは1つはNIB /ストーリーボードを作成しました)。
アウトラインを作成する構成の目的は、NIB /ストーリーボードがインスタンス化したビューコントローラーへの参照をアプリデリゲートに提供することだけです。