作为Objective-c,cocoa和iPhone开发人员的新手,我强烈希望充分利用语言和框架.
我正在使用的资源之一是斯坦福大学的CS193P课程,他们已经留在网上了.它包括讲义,作业和示例代码,由于课程由Apple开发者提供,我绝对认为它是"从马的嘴里".
课程网站:http:
//www.stanford.edu/class/cs193p/cgi-bin/index.php
第08讲与构建基于UINavigationController的应用程序的任务相关,该应用程序将多个UIViewControllers推送到UINavigationController堆栈.这就是UINavigationController的工作原理.这是合乎逻辑的.但是,幻灯片中有一些关于UIViewControllers之间通信的严厉警告.
我将引用这个严肃的幻灯片:http:
//cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf
第16/51页:
如何不共享数据
全局变量或单例
这包括您的申请代表
直接依赖使您的代码不再可重用
而且更难以调试和测试
好.我很失望.不要盲目地将所有用于在viewcontroller之间进行通信的方法扔到app委托中,并引用app delegate方法中的viewcontroller实例.公平的'努力.
再进一步说,我们得到这张幻灯片告诉我们应该做些什么.
第18/51页:
数据流的最佳实践
弄清楚究竟需要传达什么
定义视图控制器的输入参数
要进行备份层次结构的通信,请使用松散耦合
为观察者定义通用接口(如委托)
然后,这张幻灯片后面会出现一个占位符幻灯片,然后讲师显然会使用UIImagePickerController的示例演示最佳实践.我希望这些视频可用!:(
好吧,所以...我担心我的objc-fu不是那么强大.我也对上面引用的最后一行感到困惑.我一直在谷歌搜索关于这一点,我发现似乎是一篇体面的文章谈论观察/通知技术的各种方法:http:
//cocoawithlove.com/2008/06/five-approaches-to -listening-observing.html
方法#5甚至表示委托作为一种方法!除了....对象一次只能设置一个委托.所以当我有多个viewcontroller通信时,我该怎么办?
好的,那是设置团伙.我知道我可以通过引用在我的appdelegate中的多个viewcontroller实例轻松地在app委托中执行我的通信方法,但我想以正确的方式做这种事情.
请回答以下问题,帮助我"做正确的事":
当我试图在UINavigationController堆栈上推送一个新的viewcontroller时,谁应该这样做. 我的代码中哪个类/文件是正确的位置?
当我想在一个UIViewControllers中影响某个数据片段(iVar的值)时,当我在另一个 UIViewController中时,这样做的"正确"方法是什么?
假设我们在一个对象中一次只能设置一个委托,当讲师说"为观察者定义一个通用接口(如委托)"时,实现会是什么样子.如果可能的话,伪代码示例在这里会非常有用.
Clint Harris.. 224
这些都是很好的问题,很高兴看到你正在做这项研究,并且似乎关心学习如何"做正确"而不是仅仅将它们混合在一起.
首先,我同意之前的答案,这些答案侧重于在适当的时候将数据放入模型对象中的重要性(根据MVC设计模式).通常你想避免将状态信息放在控制器中,除非它是严格的"表示"数据.
其次,请参阅斯坦福大学演示文稿的第10页,了解如何以编程方式将控制器推入导航控制器的示例.有关如何使用Interface Builder"直观地"执行此操作的示例,请查看本教程.
第三,也许最重要的是,请注意,如果您在"依赖注入"设计模式的背景下考虑它们,斯坦福大学的演示文稿中提到的"最佳实践"会更容易理解.简而言之,这意味着您的控制器不应"查找"完成其工作所需的对象(例如,引用全局变量).相反,您应该始终将这些依赖项"注入"到控制器中(即,通过方法传递它需要的对象).
如果您遵循依赖注入模式,您的控制器将是模块化和可重用的.如果您考虑斯坦福大学主持人的来源(即,作为Apple员工,他们的工作是构建可以轻松重用的类),可重用性和模块化是高优先级.他们提到的用于共享数据的所有最佳实践都是依赖注入的一部分.
这是我的回应的要点.我将在下面提供一个使用依赖注入模式和控制器的示例,以防它有用.
使用视图控制器的依赖注入的示例
假设您正在构建一个屏幕,其中列出了几本书.用户可以选择他/她想要购买的书籍,然后点击"结账"按钮以进入结账屏幕.
要构建它,您可以创建一个BookPickerViewController类来控制和显示GUI /视图对象.它会在哪里获得所有书籍数据?让我们说它依赖于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]; ... }
当苹果公司正在谈论使用委托模式"沟通备份层次结构"时,他们仍然在谈论依赖注入.在这个例子中,一旦用户选择了他/她的书并准备结账,BookPickerViewController应该做什么?嗯,这不是真正的工作.它应该将该工作委托给其他对象,这意味着它在另一个对象上进行DEPENDS.所以我们可以修改我们的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{ ... } @end @implementation MyBookWarehouse { ... } @end @interface MyCheckoutController : NSObject { ... } @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 ... }
当我看到像这样的问题(和答案),精心打造时,我忍不住微笑.我们勇敢的提问者和你们当之无愧的荣誉!同时,我想分享您在第二点中引用的方便的invasioncode.com链接的更新链接:http://www.invasivecode.com/2009/09/implementing-a-navigation-controller-uinavigationcontroller-with-interface -builder/ - 再次感谢您分享您的见解和最佳实践,并通过示例进行备份! (19认同)
Brent Royal-.. 15
这种事总是一种品味问题.
话虽如此,我总是喜欢通过模型对象进行协调(#2).顶级视图控制器加载或创建所需的模型,每个视图控制器在其子控制器中设置属性,以告诉他们需要使用哪些模型对象.使用NSNotificationCenter将大多数更改传递回层次结构; 触发通知通常内置于模型本身.
例如,假设我有一个带有帐户和交易的应用程序.我还有一个AccountListController,一个AccountController(显示一个带有"show all transactions"按钮的帐户摘要),一个TransactionListController和一个TransactionController.AccountListController加载所有帐户的列表并显示它们.当您点击列表项时,它会设置其AccountController的.account属性并将AccountController压入堆栈.当您点击"显示所有事务"按钮时,AccountController将加载事务列表,将其放入TransactionListController的.transactions属性中,并将TransactionListController压入堆栈,依此类推.
如果,例如,TransactionController编辑事务,它会在其事务对象中进行更改,然后调用其"save"方法.'save'发送TransactionChangedNotification.当事务更改时需要刷新自身的任何其他控制器将观察通知并更新自身.大概是TransactionListController会; AccountController和AccountListController可能会取决于他们想要做什么.
对于#1,在我的早期应用程序中,我有一些displayModel:withNavigationController:子控制器中的方法,用于设置并将控制器推入堆栈.但随着我对SDK的熟悉程度越来越高,我已经远离了这一点,现在我通常让父母推动孩子.
对于#3,请考虑此示例.这里我们使用两个控制器AmountEditor和TextEditor来编辑Transaction的两个属性.编辑器实际上不应该保存正在编辑的事务,因为用户可以决定放弃该事务.所以相反,他们都将他们的父控制器作为委托,并在其上调用一个方法,说明他们是否已经改变了什么.
@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; iddelegate; } @property (retain) Model * model; @property (assign) id 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 { 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]; }
需要注意的是,我们已经定义了一个通用协议,编辑可以使用该协议与他们自己的控制器进行通信.通过这样做,我们可以在应用程序的另一部分重用编辑器.(也许Account也可以有注释.)当然,EditorDelegate协议可以包含多个方法; 在这种情况下,这是唯一必要的.
这些都是很好的问题,很高兴看到你正在做这项研究,并且似乎关心学习如何"做正确"而不是仅仅将它们混合在一起.
首先,我同意之前的答案,这些答案侧重于在适当的时候将数据放入模型对象中的重要性(根据MVC设计模式).通常你想避免将状态信息放在控制器中,除非它是严格的"表示"数据.
其次,请参阅斯坦福大学演示文稿的第10页,了解如何以编程方式将控制器推入导航控制器的示例.有关如何使用Interface Builder"直观地"执行此操作的示例,请查看本教程.
第三,也许最重要的是,请注意,如果您在"依赖注入"设计模式的背景下考虑它们,斯坦福大学的演示文稿中提到的"最佳实践"会更容易理解.简而言之,这意味着您的控制器不应"查找"完成其工作所需的对象(例如,引用全局变量).相反,您应该始终将这些依赖项"注入"到控制器中(即,通过方法传递它需要的对象).
如果您遵循依赖注入模式,您的控制器将是模块化和可重用的.如果您考虑斯坦福大学主持人的来源(即,作为Apple员工,他们的工作是构建可以轻松重用的类),可重用性和模块化是高优先级.他们提到的用于共享数据的所有最佳实践都是依赖注入的一部分.
这是我的回应的要点.我将在下面提供一个使用依赖注入模式和控制器的示例,以防它有用.
使用视图控制器的依赖注入的示例
假设您正在构建一个屏幕,其中列出了几本书.用户可以选择他/她想要购买的书籍,然后点击"结账"按钮以进入结账屏幕.
要构建它,您可以创建一个BookPickerViewController类来控制和显示GUI /视图对象.它会在哪里获得所有书籍数据?让我们说它依赖于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]; ... }
当苹果公司正在谈论使用委托模式"沟通备份层次结构"时,他们仍然在谈论依赖注入.在这个例子中,一旦用户选择了他/她的书并准备结账,BookPickerViewController应该做什么?嗯,这不是真正的工作.它应该将该工作委托给其他对象,这意味着它在另一个对象上进行DEPENDS.所以我们可以修改我们的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{ ... } @end @implementation MyBookWarehouse { ... } @end @interface MyCheckoutController : NSObject { ... } @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).顶级视图控制器加载或创建所需的模型,每个视图控制器在其子控制器中设置属性,以告诉他们需要使用哪些模型对象.使用NSNotificationCenter将大多数更改传递回层次结构; 触发通知通常内置于模型本身.
例如,假设我有一个带有帐户和交易的应用程序.我还有一个AccountListController,一个AccountController(显示一个带有"show all transactions"按钮的帐户摘要),一个TransactionListController和一个TransactionController.AccountListController加载所有帐户的列表并显示它们.当您点击列表项时,它会设置其AccountController的.account属性并将AccountController压入堆栈.当您点击"显示所有事务"按钮时,AccountController将加载事务列表,将其放入TransactionListController的.transactions属性中,并将TransactionListController压入堆栈,依此类推.
如果,例如,TransactionController编辑事务,它会在其事务对象中进行更改,然后调用其"save"方法.'save'发送TransactionChangedNotification.当事务更改时需要刷新自身的任何其他控制器将观察通知并更新自身.大概是TransactionListController会; AccountController和AccountListController可能会取决于他们想要做什么.
对于#1,在我的早期应用程序中,我有一些displayModel:withNavigationController:子控制器中的方法,用于设置并将控制器推入堆栈.但随着我对SDK的熟悉程度越来越高,我已经远离了这一点,现在我通常让父母推动孩子.
对于#3,请考虑此示例.这里我们使用两个控制器AmountEditor和TextEditor来编辑Transaction的两个属性.编辑器实际上不应该保存正在编辑的事务,因为用户可以决定放弃该事务.所以相反,他们都将他们的父控制器作为委托,并在其上调用一个方法,说明他们是否已经改变了什么.
@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; iddelegate; } @property (retain) Model * model; @property (assign) id 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 { 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]; }
需要注意的是,我们已经定义了一个通用协议,编辑可以使用该协议与他们自己的控制器进行通信.通过这样做,我们可以在应用程序的另一部分重用编辑器.(也许Account也可以有注释.)当然,EditorDelegate协议可以包含多个方法; 在这种情况下,这是唯一必要的.