web-dev-qa-db-ja.com

代議員対iPhoneOSでの通知

子ビューコントローラーからルートビューコントローラーのメソッドを呼び出そうとしています。オプションを変更すると、ルートビューが自動的に更新され、他のいくつかのビューコントローラーが更新されます。 2番目の部分では通知を使用しましたが、最初の部分ではデリゲートを使用しようとしています。これは、デリゲートが優れたプログラミング手法であるためです。私はそれを機能させるのに苦労していて、仕事をするために別の通知を簡単に設定できることを知っています。デリゲートの実装を続行する必要がありますか、それとも通知を使用するだけですか?

33

委任は多くの状況で優れたプログラミング手法ですが、それに慣れていない場合は委任を使用する必要があるという意味ではありません。委任と通知の両方が、ViewControllerを相互に分離するのに役立ちます。これは良いことです。通知はコーディングが少し簡単で、複数のオブジェクトが1つの通知を監視できるという利点があります。デリゲートでは、デリゲートオブジェクトを変更せずにそのようなことを行うことはできません(そして珍しいことです)。

委任のいくつかの利点:

  • 委任オブジェクトと委任の間の関係は、特に委任の実装が必須である場合に、より明確になります。
  • 複数のタイプのメッセージをデリゲートからデリゲートに渡す必要がある場合、デリゲートでは、メッセージごとに1つのデリゲートメソッドを指定することで、これを明確にすることができます。 通知の場合、複数の通知名を使用できますが、すべての通知はオブザーバー側で同じメソッドになります(厄介なswitchステートメントが必要になる可能性があります)。

どのパターンが自分に適しているかを判断できるのはあなただけです。いずれの場合も、ViewControllerに通知またはデリゲートメッセージを送信させないことを検討する必要があります。多くの場合、View Controllerはモデルを変更する必要があり、次にモデルは変更されたことをオブザーバーまたはデリゲートに通知する必要があります。

デリゲートパターンの実装は簡単です。

  1. ChildViewController.hで、デリゲートが後で実装する必要があるデリゲートプロトコルを宣言します。

    @protocol ChildViewControllerDelegate <NSObject>
    @optional
    - (void)viewControllerDidChange:(ChildViewController *)controller;
    @end
    
  2. ファイルの先頭に、ChildViewControllerのデリゲートへのポインターを保持するインスタンス変数を作成します。

    @protocol ChildViewControllerDelegate;
    @interface ChildViewController : UIViewController {
        id <ChildViewControllerDelegate> delegate;
        ...
    }
    @property (assign) id <ChildViewControllerDelegate> delegate;
    ...
    @end
    
  3. RootViewController.hで、クラスをデリゲートプロトコルに準拠させます。

    @interface RootViewController : UIViewController <ChildViewControllerDelegate> {
    ...
    
  4. RootViewController実装で、デリゲートメソッドを実装します。また、ChildViewControllerインスタンスを作成するときに、デリゲートを割り当てる必要があります。

    @implement RootViewController
    ...
    // in some method:
    ChildViewController *controller = [[ChildViewController alloc] initWithNibName:...
    controller.delegate = self;
    ...
    - (void)viewControllerDidChange:(ChildViewController *)controller {
        NSLog(@"Delegate method was called.");
    }
    ...
    
  5. ChildViewController実装では、適切なタイミングでデリゲートメソッドを呼び出します。

    @implementation ChildViewController
    ...
    // in some method:
    if ([self.delegate respondsToSelector:@selector(viewControllerDidChange:)]) {
        [self.delegate viewControllerDidChange:self];
    }
    ...
    

それでおしまい。 (注:これはメモリから書き込んだので、おそらくいくつかのタイプミス/バグがあります。)

58
Ole Begemann

追加したい:

通知を受信するオブジェクトは、イベントが発生した後にのみ反応できます。これは、委任との大きな違いです。デリゲートには、デリゲートオブジェクトによって提案された操作を拒否または変更する機会が与えられます。一方、オブジェクトを観察することは、差し迫った操作に直接影響を与えることはできません。

25

通常、モデル内のデータの変更に基づいてUIを更新する必要がある場合は、ビューコントローラーに関連するモデルデータを監視させ、変更が通知されたときにビューを更新します。

委任はもう少し正式で、 Peter Hosey 最近共有された区別が好きだと思います。

違いは、委任は1対1(および双方向)の通信用であるのに対し、通知は1対多の単方向通信用であるということです。

また、viewWillAppear:のビューを(完全に)更新すると正常に機能することがわかりました(ただし、パフォーマンスが懸念される場合、これは最善の解決策ではありません)。

11
gerry3

通知により、プログラムの実行時の動作が大幅に複雑になる可能性があります。複数の宛先を持つgotoのように考えてください。それらの宛先の順序は定義されていません。クラッシュした場合、スタックトレース情報はほとんどありません。

通知を使用することが理にかなっている場合があります。通常は、モデルの変更またはグローバルな状態の変更をビューに通知することです。たとえば、ネットワークがダウンしている、アプリケーションが辞任するなどです。

IOSでデリゲートパターンを学ぶことは価値があります。デリゲートは、デバッグ時に完全なスタックトレースを提供します。これらにより、オブジェクトを分離するという目標を達成しながら、実行時の動作が大幅に簡素化されます。

6
Ray Fix

デリゲートは慣れるのが少し難しいですが、それがベストプラクティスだと思います。Appleのように彼らはうまく機能します

私は常に正式なプロトコル宣言を使用します。私の考えではもう少し論理的であり、コードでは非常に明確です。 コントローラーの代わりにUIViewを使用してオプションを変更することをお勧めします。私は常に1つのメインコントローラーを使用し、1つのコントローラーが制御できるサブクラス化されたUIViewを多数持っています。(ただし、次のコードは変更できますコントローラーの場合、通常のビューの代わりにコントローラーが本当に必要な場合)子ビューのヘッダーファイルで、次のようにします。

// ChildView.h
#import <UIKit/UIKit.h>

@protocol ChildViewDelegate; // tells the compiler that there will be a protocol definition later

@interface ChildViewController : UIView {
    id <ChildViewDelegate> delegate;
    // more stuff
}

// properties and class/instance methods

@end

@protocol ChildViewDelegate // this is the formal definition

- (void)childView:(ChildView *)c willDismissWithButtonIndex:(NSInteger)i; // change the part after (ChildView *)c to reflect the chosen options

@end

@protocolと2番目の@endの間のメソッドは、ChildViewの実装のどこかで呼び出すことができ、ルートビューコントローラーを「通知」を受け取るデリゲートにすることができます。

.mファイルは次のようになります。

// ChildView.m
#import "ChildView.h"

@implementation ChildView

- (id)initWithDelegate:(id<ChildViewDelegate>)del { // make this whatever you want
    if (self = [super initWithFrame:CGRect(0, 0, 50, 50)]) { // if frame is a parameter for the init method, you can make that here, your choice
        delegate = del; // this defines what class listens to the 'notification'
    }
    return self;
}

// other methods

// example: a method that will remove the subview

- (void)dismiss {
    // tell the delegate (listener) that you're about to dismiss this view
    [delegate childView:self willDismissWithButtonIndex:3];
    [self removeFromSuperView];
}

@end

その場合、ルートビューコントローラーの.hファイルには次のコードが含まれます。

// RootViewController.h
#import "ChildView.h"

@interface RootViewController : UIViewController <ChildViewDelegate> {
    // stuff
}

// stuff

@end

また、実装ファイルは、ChildView.hのプロトコルで定義されたメソッドを実装します。これは、ChildViewが実行を要求したときに実行されるためです。その方法では、通知を受け取ったときに発生することを入れます。

2
Arseniy Banayev

この場合、ビュー間で直接通信する必要がないため、委任や通知を使用する必要はありません。 gerry3が言ったように、データモデル自体を変更してから、他のすべてのビューがその変更に応答できるようにする必要があります。

データモデルは、すべてのViewControllerがアクセスできる独立したオブジェクトである必要があります。 (怠惰な方法は、アプリデリゲートの属性としてパークすることです。)ユーザーがビューAで変更を行うと、ビューAのコントローラーはその変更をデータモデルに書き込みます。次に、ビューBからZが開くたびに、それらのコントローラーがデータモデルを読み取り、ビューを適切に構成します。

このように、ビューもそのコントローラーもお互いを認識する必要はなく、すべての変更は1つの中央オブジェクトで発生するため、簡単に追跡できます。

0
TechZen

ルートビューコントローラーが変更について知る必要がありますか、それともサブビューだけですか?

ルートコントローラーが知る必要がない場合、他のビューが探している通知を設定で送信することは、コードを単純化するので、私にとってより良い答えのように思えます。必要以上に複雑さを導入する必要はありません。