web-dev-qa-db-ja.com

AppDelegateからメソッドを呼び出す-Objective-C

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];
12
lucas_turci

背景情報(クラス定義とクラスインスタンス)

ここで重要な概念は、クラス定義とクラスインスタンスの違いです。

クラス定義は、クラスのソースコードです。たとえば、ViewController.mにはmyViewControllerクラスの定義が含まれ、AppDelegate.mにはAppDelegateクラスの定義が含まれます。あなたの質問で言及されている他のクラスはUIApplicationです。これはシステム定義のクラスです。つまり、そのクラスのソースコードがありません。

クラスインスタンスは、ヒープ上のメモリのチャンクであり、そのメモリへのポインタです。クラスインスタンスは通常、次のようなコード行で作成されます

myClass *foo = [[myClass alloc] init];

allocはクラスのヒープ上にスペースを予約し、次にinitはクラスの変数/プロパティの初期値を設定することに注意してください。インスタンスへのポインタは、fooに格納されます。

アプリケーションが起動すると、次の一連のイベントが発生します(大まかに言えば)。

  • システムはUIApplicationクラスのインスタンスを作成します
  • uIApplicationインスタンスへのポインタは、システム変数のどこかに格納されます
  • システムはAppDelegateクラスのインスタンスを作成します
  • appDelegateへのポインターは、UIApplicationインスタンスのdelegateという変数に格納されます。
  • システムはMyViewControllerクラスのインスタンスを作成します
  • myViewControllerクラスへのポインタはどこかに保存されています

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メソッドを直接呼び出すようにシステムに指示します。

17
user3386109

ここに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];
}
8
kkumpavat

ビューコントローラは、NIB /ストーリーボードプロセスの一部としてすでにインスタンス化されているため、アプリデリゲートが独自のalloc/initを実行する場合は、単に別のインスタンスを作成しているだけです(これは1つはNIB /ストーリーボードを作成しました)。

アウトラインを作成する構成の目的は、NIB /ストーリーボードがインスタンス化したビューコントローラーへの参照をアプリデリゲートに提供することだけです。

4
Rob