165

Objective-C、ココア、iPhone 開発全般に不慣れな私は、言語とフレームワークを最大限に活用したいという強い願望を持っています。

私が使用しているリソースの 1 つは、Web 上に残されているスタンフォード大学の CS193P クラス ノートです。講義ノート、課題、サンプル コードが含まれており、このコースは Apple 開発者によって提供されたものなので、間違いなく「馬の口から」だと思います。

クラスのウェブサイト:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

講義 08 は、UINavigationController スタックにプッシュされた複数の UIViewController を持つ UINavigationController ベースのアプリを構築する課題に関連しています。これが UINavigationController の仕組みです。それは論理的です。ただし、このスライドには、UIViewController 間の通信に関するいくつかの厳しい警告があります。

この深刻なスライドから引用します:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

ページ 16/51:

データを共有しない方法

  • グローバル変数またはシングルトン
    • これには、アプリケーション デリゲートが含まれます
  • 直接的な依存関係により、コードの再利用性が低下します
    • デバッグとテストがより困難

Ok。私はそれでダウンしています。ビューコントローラー間の通信に使用されるすべてのメソッドをやみくもにアプリデリゲートに投げ込み、アプリデリゲートメソッドでビューコントローラーインスタンスを参照しないでください。公正な「ナフ。

もう少し進むと、このスライドで何をすべきかがわかります。

ページ 18/51:

データ フローのベスト プラクティス

  • 何を伝える必要があるかを正確に把握する
  • ビュー コントローラーの入力パラメーターを定義する
  • 階層をバックアップする通信には、疎結合を使用します
    • オブザーバー用の汎用インターフェースを定義する (委譲など)

このスライドの後には、講師が UIImagePickerController の例を使用してベスト プラクティスを明らかに示すプレース ホルダー スライドのように見えるものが続きます。動画があればいいのに!:(

わかりました、それで... 残念ながら、私の objc-fu はそれほど強力ではありません。また、上記の引用の最後の行にも少し混乱しています。私はこれについてかなりの割合でグーグル検索を行ってきましたが、観察/通知技術のさまざまな方法について話しているまともな記事のように見えるものを見つけました:
http://cocoawithlove.com/2008/06/five-approaches-to -listening-observing.html

メソッド #5 はデリゲートをメソッドとして示しています! ただし、オブジェクトは一度に 1 つのデリゲートしか設定できません。では、複数のビューコントローラー通信を行う場合、どうすればよいでしょうか?

わかりました、それがセットアップ ギャングです。appdelegate の複数の viewcontroller インスタンスを参照することで、アプリ デリゲートで通信メソッドを簡単に実行できることはわかっていますが、この種のことを正しい方法で実行したいと考えています。

次の質問に答えて、「正しいことをする」のを手伝ってください。

  1. UINavigationController スタックに新しいビューコントローラーをプッシュしようとすると、がこのプッシュを行う必要がありますか。 コード内のどのクラス/ファイルが正しい場所ですか?
  2. の UIViewControllerにいるときに、UIViewController の 1 つのデータ (iVar の値) に影響を与えたい場合、これを行う「正しい」方法は何ですか?
  3. オブジェクトに一度に設定できるデリゲートは 1 つだけだとすると、講師が「オブザーバー用の汎用インターフェイス (デリゲートなど) を定義する」と言った場合、実装はどのようになるでしょうか。疑似コードの例は、可能であればここで非常に役立ちます。
4

4 に答える 4

225

これらは良い質問であり、あなたがこの研究を行っており、単にハッキングするのではなく、「正しく行う」方法を学ぶことに関心を持っているように見えるのは素晴らしいことです.

まず、適切な場合にモデルオブジェクトにデータを配置することの重要性に焦点を当てた以前の回答に同意します(MVC設計パターンに従って)。通常、厳密に「プレゼンテーション」データでない限り、コントローラー内に状態情報を配置することは避けたいと考えています。

2 つ目は、Stanford プレゼンテーションの 10 ページを参照して、プログラミングによってコントローラーをナビゲーション コントローラーにプッシュする方法の例を確認してください。Interface Builder を使用してこれを「視覚的に」行う方法の例については、このチュートリアルをご覧ください。

3 番目に、おそらく最も重要なこととして、スタンフォード大学のプレゼンテーションで言及された「ベスト プラクティス」は、「依存性注入」設計パターンのコンテキストで考えると、はるかに理解しやすいことに注意してください。簡単に言えば、これは、コントローラーがその仕事をするために必要なオブジェクトを「ルックアップ」してはならないことを意味します (たとえば、グローバル変数を参照します)。代わりに、これらの依存関係を常にコントローラーに「注入」する必要があります (つまり、メソッドを介して必要なオブジェクトを渡します)。

依存性注入パターンに従えば、コントローラーはモジュール化され、再利用可能になります。また、スタンフォード大学のプレゼンターがどこから来ているかを考えると (つまり、Apple の従業員として、簡単に再利用できるクラスを作成するのが彼らの仕事です)、再利用性とモジュール性が最優先事項です。彼らが言及するデータ共有のベスト プラクティスはすべて、依存性注入の一部です。

それが私の回答の要旨です。参考になる場合に備えて、コントローラーで依存性注入パターンを使用する例を以下に示します。

View Controllerで依存性注入を使用する例

いくつかの本がリストされている画面を作成しているとしましょう。ユーザーは、購入したい本を選び、「チェックアウト」ボタンをタップしてチェックアウト画面に移動できます。

これを構築するには、GUI/ビュー オブジェクトを制御および表示する BookPickerViewController クラスを作成します。すべての書籍データはどこで取得されますか? そのための BookWarehouse オブジェクトに依存しているとしましょう。これで、コントローラーは基本的に、モデル オブジェクト (BookWarehouse) と GUI/ビュー オブジェクトの間でデータを仲介します。つまり、BookPickerViewController は BookWarehouse オブジェクトに依存します。

これをしないでください:

@implementation BookPickerViewController

-(void) doSomething {
   // I need to do something with the BookWarehouse so I'm going to look it up
   // using the BookWarehouse class method (comparable to a global variable)
   BookWarehouse *warehouse = [BookWarehouse getSingleton];
   ...
}

代わりに、依存関係を次のように注入する必要があります。

@implementation BookPickerViewController

-(void) initWithWarehouse: (BookWarehouse*)warehouse {
   // myBookWarehouse is an instance variable
   myBookWarehouse = warehouse;
   [myBookWarehouse retain];
}

-(void) doSomething {
   // I need to do something with the BookWarehouse object which was 
   // injected for me
   [myBookWarehouse listBooks];
   ...
}

Apple 関係者が委譲パターンを使用して「階層をバックアップする」ことについて話しているとき、彼らはまだ依存性注入について話している。この例では、ユーザーが自分の本を選んでチェックアウトする準備ができたら、BookPickerViewController は何をすべきでしょうか? まあ、それは本当の仕事ではありません。他のオブジェクトに機能することを委任する必要があります。つまり、別のオブジェクトに依存することを意味します。したがって、BookPickerViewController の init メソッドを次のように変更できます。

@implementation BookPickerViewController

-(void) initWithWarehouse:    (BookWarehouse*)warehouse 
        andCheckoutController:(CheckoutController*)checkoutController 
{
   myBookWarehouse = warehouse;
   myCheckoutController = checkoutController;
}

-(void) handleCheckout {
   // We've collected the user's book picks in a "bookPicks" variable
   [myCheckoutController handleCheckout: bookPicks];
   ...
}

これらすべての最終的な結果として、BookPickerViewController クラス (および関連する GUI/ビュー オブジェクト) を提供していただければ、BookWarehouse と CheckoutController が実装可能な汎用インターフェイス (つまり、プロトコル) であると仮定して、自分のアプリケーションで簡単に使用できるようになります。 :

@interface MyBookWarehouse : NSObject <BookWarehouse> { ... } @end
@implementation MyBookWarehouse { ... } @end

@interface MyCheckoutController : NSObject <CheckoutController> { ... } @end
@implementation MyCheckoutController { ... } @end

...

-(void) applicationDidFinishLoading {
   MyBookWarehouse *myWarehouse = [[MyBookWarehouse alloc]init];
   MyCheckoutController *myCheckout = [[MyCheckoutController alloc]init];

   BookPickerViewController *bookPicker = [[BookPickerViewController alloc] 
                                         initWithWarehouse:myWarehouse 
                                         andCheckoutController:myCheckout];
   ...
   [window addSubview:[bookPicker view]];
   [window makeKeyAndVisible];
}

最後に、BookPickerController は再利用可能であるだけでなく、テストも容易です。

-(void) testBookPickerController {
   MockBookWarehouse *myWarehouse = [[MockBookWarehouse alloc]init];
   MockCheckoutController *myCheckout = [[MockCheckoutController alloc]init];

   BookPickerViewController *bookPicker = [[BookPickerViewController alloc] initWithWarehouse:myWarehouse andCheckoutController:myCheckout];
   ...
   [bookPicker handleCheckout];

   // Do stuff to verify that BookPickerViewController correctly called
   // MockCheckoutController's handleCheckout: method and passed it a valid
   // list of books
   ...
}
于 2009-02-22T19:17:34.873 に答える
15

この種のことは、常に好みの問題です。

そうは言っても、私は常にモデル オブジェクトを介して調整 (#2) を行うことを好みます。最上位のビュー コントローラーは、必要なモデルをロードまたは作成し、各ビュー コントローラーは子コントローラーにプロパティを設定して、操作する必要があるモデル オブジェクトを伝えます。ほとんどの変更は、NSNotificationCenter を使用して階層をバックアップして伝達されます。通常、通知の起動はモデル自体に組み込まれています。

たとえば、Accounts と Transactions を備えたアプリがあるとします。また、AccountListController、AccountController (「すべてのトランザクションを表示」ボタンでアカウントの概要を表示する)、TransactionListController、および TransactionController もあります。AccountListController は、すべてのアカウントのリストをロードして表示します。リスト項目をタップすると、その AccountController の .account プロパティが設定され、AccountController がスタックにプッシュされます。「すべてのトランザクションを表示」ボタンをタップすると、AccountController はトランザクション リストをロードし、それを TransactionListController の .transactions プロパティに入れ、TransactionListController をスタックにプッシュします。

たとえば、TransactionController がトランザクションを編集する場合、そのトランザクション オブジェクトに変更を加えてから、「保存」メソッドを呼び出します。「保存」は、TransactionChangedNotification を送信します。トランザクションが変更されたときにそれ自体を更新する必要がある他のコントローラーは、通知を監視し、それ自体を更新します。TransactionListController はおそらくそうするでしょう。AccountController と AccountListController は、何をしようとしているかによって異なります。

#1 については、私の初期のアプリでは、子コントローラーにある種の displayModel:withNavigationController: メソッドがありました。このメソッドは、設定を行い、コントローラーをスタックにプッシュします。しかし、SDK に慣れるにつれて、SDK から離れていき、今では通常、親が子をプッシュするようにしています。

#3 については、次の例を検討してください。ここでは、トランザクションの 2 つのプロパティを編集するために、AmountEditor と TextEditor の 2 つのコントローラーを使用しています。ユーザーがトランザクションを放棄する可能性があるため、編集者は編集中のトランザクションを実際に保存しないでください。代わりに、両方とも親コントローラーをデリゲートとして受け取り、何かを変更したかどうかを示すメソッドを呼び出します。

@class Editor;
@protocol EditorDelegate
// called when you're finished.  updated = YES for 'save' button, NO for 'cancel'
- (void)editor:(Editor*)editor finishedEditingModel:(id)model updated:(BOOL)updated;  
@end

// this is an abstract class
@interface Editor : UIViewController {
    id model;
    id <EditorDelegate> delegate;
}
@property (retain) Model * model;
@property (assign) id <EditorDelegate> delegate;

...define methods here...
@end

@interface AmountEditor : Editor
...define interface here...
@end

@interface TextEditor : Editor
...define interface here...
@end

// TransactionController shows the transaction's details in a table view
@interface TransactionController : UITableViewController <EditorDelegate> {
    AmountEditor * amountEditor;
    TextEditor * textEditor;
    Transaction * transaction;
}
...properties and methods here...
@end

次に、TransactionController のいくつかのメソッド:

- (void)viewDidLoad {
    amountEditor.delegate = self;
    textEditor.delegate = self;
}

- (void)editAmount {
    amountEditor.model = self.transaction;
    [self.navigationController pushViewController:amountEditor animated:YES];
}

- (void)editNote {
    textEditor.model = self.transaction;
    [self.navigationController pushViewController:textEditor animated:YES];
}

- (void)editor:(Editor*)editor finishedEditingModel:(id)model updated:(BOOL)updated {
    if(updated) {
        [self.tableView reloadData];
    }

    [self.navigationController popViewControllerAnimated:YES];
}

注意すべきことは、エディターが所有するコントローラーと通信するために使用できる汎用プロトコルを定義したことです。そうすることで、アプリケーションの別の部分でエディターを再利用できます。(おそらく、アカウントもメモを持つことができます。) もちろん、EditorDelegate プロトコルには複数のメソッドを含めることができます。この場合、必要なのはこれだけです。

于 2009-02-22T01:37:57.573 に答える
0

AとBの2つのクラスがあるとします。

クラスAのインスタンスは

インスタンス。

クラス A は、クラス B のインスタンスを作成します。

B bInstance;

クラス B のロジックでは、どこかでクラス A のメソッドを通信またはトリガーする必要があります。

1) 間違った方法

aInstance を bInstance に渡すことができます。ここで、目的のメソッド [aInstance methodname] の呼び出しを bInstance の目的の場所から配置します。

これはあなたの目的を果たしたでしょうが、解放するとメモリがロックされて解放されなくなります。

どのように?

aInstance を bInstance に渡すと、aInstance の保持カウントが 1 増加しました。bInstance の割り当てを解除すると、bInstance 自体が aInstance のオブジェクトであるという理由で、bInstance によって保持カウントを 0 にすることはできないため、メモリがブロックされます。

また、aInstance がスタックしているため、bInstance のメモリもスタック (リーク) します。そのため、後で aInstance 自体の割り当てを解除した後でも、bInstance は解放できず、bInstance は aInstance のクラス変数であるため、そのメモリもブロックされます。

2) 正しい方法

aInstance を bInstance のデリゲートとして定義することにより、retaincount の変更や aInstance のメモリのもつれがなくなります。

bInstance は、aInstance にあるデリゲート メソッドを自由に呼び出すことができます。bInstance の解放では、すべての変数が独自に作成され、解放されます。 aInstance の解放では、bInstance で aInstance のもつれがないため、きれいに解放されます。

于 2012-03-05T08:38:23.293 に答える
0

私はあなたの問題を見ます..

起こったことは、誰かが MVC アーキテクチャの考えを混乱させたということです。

MVC には、モデル、ビュー、およびコントローラーの 3 つの部分があります。記載されている問題は、正当な理由もなく、そのうちの 2 つを組み合わせたようです。ビューとコントローラーは別々のロジックです。

だから...複数のビューコントローラーを持ちたくない..

複数のビューと、それらの間で選択するコントローラーが必要です。(複数のアプリケーションがある場合は、複数のコントローラーを使用することもできます)

ビューは決定を下すべきではありません。コントローラーはそれを行う必要があります。したがって、タスクとロジックの分離、そしてあなたの人生を楽にする方法です。

だから..ビューがそれを行うことを確認し、データの素晴らしいビューを出力します。データの処理方法と使用するビューをコントローラに決定させます。

(そして、データについて話すときは、モデルについて話しています...保存、アクセス、変更する標準的な方法です.分割して忘れることができる別のロジックの一部です)

于 2009-02-22T08:21:54.513 に答える