视图控制器之间进行通信的最佳方式是什么?
作为Objective-C,cocoa和iPhone开发者的新手,我强烈渴望从语言和框架中获得最大的收益。
我正在使用的资源之一是斯坦福大学的CS193P课堂笔记,他们已经离开了networking。 它包括讲义,作业和示例代码,由于课程是由苹果开发者提供的,我绝对认为它是“从马口”。
class级网站:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php
08讲座涉及到一个分配,build立一个基于UINavigationController的应用程序,有多个UIViewControllers推到UINavigationController堆栈上。 这就是UINavigationController的工作原理。 这是合乎逻辑的。 但是,幻灯片中有一些关于你的UIViewControllers之间的沟通的严重警告。
我要引用这个严肃的幻灯片:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf
页面16/51:
如何不共享数据
- 全局variables或单身人士
- 这包括您的应用程序委托
- 直接的依赖性使得你的代码更加可重用
- 而且更难debugging
好。 我对此感到失望。 不要盲目折腾所有用于viewcontroller与应用程序委托之间的通信的方法,并在应用程序委托方法中引用viewcontroller实例。 公平'nuff。
再进一步,我们得到这张幻灯片告诉我们该怎么做。
页面18/51:
数据stream的最佳实践
- 找出需要传达的信息
- 为您的视图控制器定义input参数
- 为了进行通信备份,请使用松耦合
- 为观察者定义一个通用接口(如委派)
这张幻灯片之后是一个看起来像一个占位符的幻灯片,然后讲师用UIImagePickerController的例子显然演示了最佳实践。 我希望video可用! 🙁
好的,所以…恐怕我的东西不太强。 上述报价中的最后一行我也有点困惑。 我在这方面一直在做相关的search,我发现看起来像是一篇体面的文章,谈论观察/通知技术的各种方法:
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html
方法#5甚至指示代表作为一种方法! 除了….对象一次只能设置一个委托。 所以当我有多个viewcontroller通信,我该怎么办?
好的,那就是组build团伙。 我知道我可以在appdelegate中通过引用多个viewcontroller实例轻松地在应用程序委托中完成我的通信方法,但是我想以正确的方式执行这种事情。
请回答以下问题,帮助我“做正确的事情”:
- 当我尝试在UINavigationController堆栈上推送一个新的viewcontroller时, 谁应该这样做。 我的代码中哪个类/文件是正确的地方?
- 当我在一个不同的 UIViewController中想要在我的一个UIViewController中影响一些数据(一个iVar的值)时,什么是“正确”的方法呢?
- 假设我们只能在一个对象中一次设置一个委托,当讲师说“为观察者定义一个通用接口(如委派)”时,实现将会是什么样子? 如果可能的话,一个伪代码示例将非常有用。
这些都是很好的问题,而且很高兴看到你正在做这个研究,并且似乎在学习如何“做正确”而不是一味地攻击它。
首先 ,我同意以前的答案,重点是在适当的时候(根据MVCdevise模式)将数据放入模型对象中。 通常你想避免把状态信息放在控制器中,除非它是严格的“表示”数据。
其次 ,请参阅Stanford演示文稿的第10页,了解如何以编程方式将控制器推入导航控制器的示例。 有关如何使用Interface Builder“直观地”执行此操作的示例,请参阅本教程 。
第三 ,也许最重要的是,请注意,如果您在“dependency injection”devise模式的背景下思考这些问题,斯坦福大学演讲中提到的“最佳实践”就更容易理解了。 简而言之,这意味着你的控制器不应该“查找”它需要做的工作(例如,引用一个全局variables)。 相反,你应该总是“注入”这些依赖关系到控制器中(即通过方法传递它需要的对象)。
如果你遵循dependency injection模式,你的控制器将是模块化的,可重用的。 如果你考虑斯坦福大学的主讲人来自哪里(即苹果公司的员工,他们的工作就是build立可以轻松重复使用的课程),那么可重用性和模块化就成为重中之重。 他们提到的共享数据的所有最佳实践都是dependency injection的一部分。
这是我的回应的要点。 我将在下面包含一个使用dependency injection模式和一个控制器的例子,以防有用。
使用视图控制器的dependency injection示例
假设你正在build立一个列出几本书的屏幕。 用户可以select他/她想要购买的书籍,然后点击“结账”button以转到结账屏幕。
为了build立这个,你可以创build一个BookPickerViewController类来控制和显示GUI /视图对象。 在哪里可以得到所有的图书数据? 比方说,这取决于一个BookWarehouse对象。 所以现在你的控制器基本上是在模型对象(BookWarehouse)和GUI /视图对象之间进行数据交换。 换句话说,在BookWarehouse对象上的BookPickerViewController 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]; ... }
当苹果们谈论使用委托模式来“沟通备份层次”时,他们仍在谈论dependency injection。 在这个例子中,BookPickerViewController应该在用户挑选他/她的书后做什么,并准备好退房? 那么,这不是它的工作。 它应该把这个工作交给别的对象,这意味着它取决于另一个对象。 所以我们可以修改我们的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可重用,而且更容易testing。
-(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)。 顶层视图控制器加载或创build它需要的模型,每个视图控制器在其子控制器中设置属性,告诉它们需要使用哪个模型对象。 通过使用NSNotificationCenter将大部分更改传递回层次结构; 发射通知通常是内置于模型本身。
例如,假设我有一个帐户和交易的应用程序。 我也有一个AccountListController,一个AccountController(显示一个“显示所有事务”button的帐户摘要),一个TransactionListController和一个TransactionController。 AccountListController加载所有帐户的列表并显示它们。 当你点击一个列表项时,它将设置其AccountController的.account属性,并将AccountController推入堆栈。 当点击“显示所有事务”button时,AccountController加载事务列表,将其放入其TransactionListController的.transactions属性中,并将TransactionListController推入堆栈,依此类推。
例如,如果TransactionController编辑事务,它将在事务对象中进行更改,然后调用其“保存”方法。 '保存'发送一个TransactionChangedNotification。 任何其他需要在事务更改时自行刷新的控制器都会观察通知并自行更新。 TransactionListController大概会; AccountController和AccountListController可能取决于他们正在尝试做什么。
对于#1,在我的早期的应用程序中,我有一些displayModel:在子控制器中的导航控制器:方法将设置的东西,并推动控制器到堆栈上。 但是随着我对SDK的使用变得越来越舒适,我已经远离了这种情况,现在我通常会让父母推动孩子。
对于#3,考虑这个例子。 这里我们使用两个控制器AmountEditor和TextEditor来编辑一个事务的两个属性。 编辑不应该实际保存正在编辑的交易,因为用户可能决定放弃交易。 所以相反,他们都把他们的父母控制者作为一个委托,并调用一个方法,告诉他们是否改变了任何东西。
@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协议可以包含多个方法; 在这种情况下,这是唯一必要的。
我看到你的问题..
发生了什么是有人混淆了MVC架构的想法。
MVC有三个部分..模型,视图和控制器..所陈述的问题似乎有没有合理的原因合并其中两个。 视图和控制器是单独的逻辑块。
所以…你不想有多个视图控制器..
你想拥有多个视图和一个控制器来select它们。 (如果你有多个应用程序,你也可以有多个控制器)
意见不应该作出决定。 控制器应该这样做。 因此,分工的任务,逻辑和方法,使您的生活更轻松。
所以..确保你的观点是这样做的,提出了一个很好的数据观点。 让你的控制器决定如何处理数据,以及使用哪个视图。
(当我们谈论数据的时候,我们正在谈论这个模型……一个被存储,访问,修改的好标准方法……另一个我们可以把它们分开和忘记的逻辑)
假设有两个类A和B.
A类的实例是
a实例;
A类产生和B类实例,如
B实例;
而在你的B类逻辑中,你需要在某个地方沟通或触发A类方法。
1)错误的方式
您可以将aInstance传递给bInstance。 现在从bInstance中所需的位置调用所需的方法[aInstance methodname]。
这将符合你的目的,但释放会导致内存被locking,而不是释放。
怎么样?
当你将aInstance传递给bInstance时,我们增加了aInstance的保留值1.当释放bInstance时,我们将会有内存被阻塞,因为aInstance永远不能被b保留,因为bInstance本身就是aInstance的一个对象。
此外,由于实际卡住,bInstance的记忆也将卡住(泄漏)。 所以,即使在后来的时间释放aInstance之后,它的内存也会被阻塞,因为bInstance不能被释放,bInstance是aInstance的类variables。
2)正确的方式
通过将aInstance定义为bInstance的委托,将不会有aInstance的retaincount变更或记忆纠缠。
bInstance将能够自由地调用位于aInstance中的委托方法。 在bInstance的释放中,所有的variables都将自己创build,并会在aInstance的释放中释放,因为bInstance中没有一个Instance的纠缠,它会被干净地释放。