一般的にObjective-C、Cocoa、およびiPhone開発者にとっては新しい言語であり、言語とフレームワークを最大限に活用したいという強い要望があります。
私が使用しているリソースの1つは、スタンフォード大学のCS193Pクラスのメモです。これらはWeb上に残っています。講義ノート、課題、サンプルコードが含まれており、コースはApple dev'sによって与えられたので、私は間違いなく「馬の口から」だと考えています。
クラスのウェブサイト:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php
講義08は、UINavigationControllerスタックにプッシュされた複数のUIViewControllerを持つUINavigationControllerベースのアプリを構築する割り当てに関連しています。それがUINavigationControllerの仕組みです。それは論理的です。ただし、スライドにはUIViewControllers間の通信に関する厳しい警告がいくつかあります。
この深刻なスライドから引用します。
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf
ページ16/51:
データを共有しない方法
- グローバル変数またはシングルトン
- これには、アプリケーションデリゲートが含まれます
- 直接的な依存関係により、コードの再利用性が低下します
- デバッグとテストがより困難
OK。私はそれでダウンしています。 ViewControllerからアプリデリゲートへの通信に使用されるすべてのメソッドをやみくもに投げて、アプリデリゲートメソッドでViewControllerインスタンスを参照しないでください。まあまあ。
少し先に、このスライドで、すべきを教えてください。
ページ18/51:
データフローのベストプラクティス
- 何を伝える必要があるかをexactly把握する
- View Controllerの入力パラメーターを定義
- 階層を遡って通信するには、疎結合を使用します
- オブザーバー(委任など)の汎用インターフェイスを定義する
次に、このスライドの後に、プレースホルダースライドのように見えるものが続きます。そこで、講師はUIImagePickerControllerの例を使用してベストプラクティスを明らかに示します。ビデオが利用できたらいいのに! :(
OK、だから... objc-fuがそれほど強くないのではないかと心配しています。また、上記の引用の最後の行で少し混乱しています。私はこれについてグーグルでかなりの割合を行ってきましたが、監視/通知技術のさまざまな方法について述べているまともな記事であるように見えました:
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html
メソッド#5は、デリゲートをメソッドとして示しています!ただし、...オブジェクトは一度に1つのデリゲートしか設定できません。複数のViewController通信がある場合、どうすればよいですか?
OK、それが設定されたギャングです。 appdelegateの複数のviewcontrollerインスタンスを参照することで、アプリデリゲートで簡単に通信メソッドを実行できることはわかっていますが、このようなことをright方法で行いたいと思います。
次の質問に答えて、「正しいことをする」のを手伝ってください。
これらは良い質問であり、あなたがこの研究を行っており、単に一緒にハックするのではなく、「正しく行う」方法を学ぶことに関心があるように見えることは素晴らしいことです。
最初の、モデルオブジェクトにデータを入れることの重要性に焦点を当てた以前の回答に同意します適切(MVC設計パターンごと)。通常は、厳密に「プレゼンテーション」データでない限り、コントローラー内に状態情報を入れないようにしたいです。
Second、プログラムでコントローラをプッシュする方法の例については、スタンフォードのプレゼンテーションの10ページを参照してくださいNavigation Controller。 Interface Builderを使用してこれを「視覚的に」行う方法の例については、 このチュートリアル をご覧ください。
Third、そしておそらく最も重要なことは、スタンフォードのプレゼンテーションで言及されている「ベストプラクティス」が「依存性注入」設計パターンのコンテキストでそれらについて考えると、理解しやすくなります。一言で言えば、これは、コントローラーがそのジョブを実行するために必要なオブジェクトを「ルックアップ」してはならないことを意味します(例えば、グローバル変数の参照)代わりに、これらの依存関係を常にコントローラーに「注入」する必要があります(つまり、メソッドを介して必要なオブジェクトを渡します)。
依存性注入パターンに従う場合、コントローラーはモジュール式で再利用可能になります。そして、スタンフォードのプレゼンターがどこから来ているのかを考えると(つまり、Apple従業員の仕事は簡単に再利用できるクラスを構築することです)、再利用性とモジュール性が高い優先順位です。彼らがデータを共有するために言及したベストプラクティスは、依存性注入の一部です。
それが私の回答の要点です。役立つ場合に備えて、依存性注入パターンをコントローラーで使用する例を以下に示します。
View Controllerでの依存性注入の使用例
複数の本がリストされている画面を構築しているとしましょう。ユーザーは購入したい本を選んでから、「チェックアウト」ボタンをタップしてチェックアウト画面に移動できます。
これを構築するには、GUI/viewオブジェクトを制御および表示するBookPickerViewControllerクラスを作成します。すべての書籍データをどこで取得しますか?そのためにBookWarehouseオブジェクトに依存するとしましょう。コントローラは、基本的にモデルオブジェクト(BookWarehouse)とGUI /ビューオブジェクトの間でデータを仲介しています。つまり、BookPickerViewControllerはBookWarehouseオブジェクトでDEPENDSです。
これをしないでください:
@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はユーザーが自分の本を選んでチェックアウトする準備ができましたか?さて、それは実際の仕事ではありません。他のオブジェクトに動作するように委任する必要があります、つまり、別のオブジェクトに依存することを意味します。 :
@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
...
}
この種のことは常に好みの問題です。
そうは言っても、私は常にモデルオブジェクトを介して調整(#2)を行うことを好みます。最上位のView Controllerは必要なモデルをロードまたは作成し、各View Controllerは子コントローラにプロパティを設定して、操作する必要があるモデルオブジェクトを通知します。ほとんどの変更は、NSNotificationCenterを使用して階層の後方に伝達されます。通常、通知の実行はモデル自体に組み込まれています。
たとえば、アカウントとトランザクションを備えたアプリがあるとします。 AccountListController、AccountController(「すべてのトランザクションを表示」ボタンでアカウントの概要を表示)、TransactionListController、およびTransactionControllerもあります。 AccountListControllerは、すべてのアカウントのリストをロードして表示します。リスト項目をタップすると、そのAccountControllerの.accountプロパティが設定され、AccountControllerがスタックにプッシュされます。 「すべてのトランザクションを表示」ボタンをタップすると、AccountControllerはトランザクションリストをロードし、TransactionListControllerの.transactionsプロパティに入れ、TransactionListControllerをスタックにプッシュします。
たとえば、TransactionControllerがトランザクションを編集する場合、トランザクションオブジェクトに変更を加えてから、「save」メソッドを呼び出します。 'save'はTransactionChangedNotificationを送信します。トランザクションが変更されたときにそれ自体を更新する必要がある他のコントローラーは、通知を監視し、それ自体を更新します。 TransactionListControllerはおそらくそうです。 AccountControllerとAccountListControllerは、何をしようとしていたかによって異なります。
#1の初期のアプリでは、子コントローラーに何かを設定し、コントローラーをスタックにプッシュするdisplayModel:withNavigationController:メソッドがありました。しかし、SDKに慣れてきたので、私はそこから離れ、今では通常、親が子をプッシュしています。
#3については、この例を検討してください。ここでは、AmountEditorとTextEditorの2つのコントローラーを使用して、トランザクションの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プロトコルには複数のメソッドを含めることができます。この場合、必要なのはそれだけです。
2つのクラスAとBがあるとします。
クラスAのインスタンスは
AInstance;
クラスAは、クラスBのインスタンスを作成します。
B bInstance;
クラスBのロジックのどこかで、クラスAのメソッドを通信またはトリガーする必要があります。
1)間違った方法
AInstanceをbInstanceに渡すことができます。ここで、bInstanceの目的の場所から目的のメソッド[aInstance methodname]を呼び出します。
これはあなたの目的にかなっていましたが、解放するとメモリがロックされ、解放されませんでした。
どうやって?
AInstanceをbInstanceに渡すと、aInstanceのretaincountが1増加しました。bInstanceの割り当てを解除すると、bInstanceがaInstanceのオブジェクトであるため、bInstanceの理由でaInstanceを0 retaincountにできないため、メモリがブロックされます。
さらに、aInstanceがスタックしているため、bInstanceのメモリもスタック(リーク)します。そのため、aInstance自体の割り当てを解除した後でも、bInstanceは解放できず、bInstanceはaInstanceのクラス変数であるため、そのメモリもブロックされます。
2)右方向
AInstanceをbInstanceのデリゲートとして定義することにより、aInstanceの保持カウントの変更やメモリの絡み合いがなくなります。
bInstanceは、aInstanceにあるデリゲートメソッドを自由に呼び出すことができます。 bInstanceの割り当て解除では、すべての変数が独自に作成され、解放されます。aInstanceの割り当て解除では、bInstanceにaInstanceが絡まないため、きれいに解放されます。
あなたの問題がわかりました。
起こったことは、誰かがMVCアーキテクチャの考えを混乱させたことです。
MVCには、モデル、ビュー、コントローラーの3つの部分があります。上記の問題は、正当な理由もなく2つを組み合わせているようです。ビューとコントローラーは別々のロジックです。
だから...あなたは複数のView Controllerを持ちたくない。
複数のビューと、それらを選択するコントローラーが必要です。 (複数のアプリケーションがある場合は、複数のコントローラーを使用することもできます)
ビューは決定を下すべきではありません。コントローラーがそれを行う必要があります。したがって、タスク、ロジック、およびあなたの人生を楽にする方法の分離。
だから..あなたのビューがそれをしていることを確認し、データのニースビューを出す。コントローラーにデータの処理方法と使用するビューを決定させます。
(そして、データについて話すときは、モデルについて話します...保存、アクセス、修正される素敵な標準的な方法です。別のロジックを分割して忘れることができます)