视图控制器之间进行通信的最佳方式是什么?

一般来说,作为Objective-C,可可和iPhone开发人员的新手,我非常希望充分利用语言和框架。

我正在使用的资源之一是斯坦福大学的CS193P课堂笔记,他们已将其留在网上。 它包括讲义,作业和示例代码,并且由于课程是由苹果开发者提供的,所以我绝对认为它是“从马的嘴巴”。

班级网站:
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页:

如何不共享数据

  • 全局变量或单身人士
  • 这包括你的应用程序委托
  • 直接的依赖性使得你的代码更加可重用
  • 更难以调试和测试
  • 好。 我对此感到失望。 不要盲目折腾所有用于将viewcontroller与您的应用程序委托进行通信的方法,并在应用程序委托方法中引用viewcontroller实例。 公平'nuff。

    再进一步,我们得到这张幻灯片告诉我们该怎么做。

    页面18/51:

    数据流的最佳实践

  • 确切地说明需要传达的信息
  • 为视图控制器定义输入参数
  • 为了交流备份层次结构,请使用松耦合
  • 为观察者定义一个通用接口(如委派)
  • 这张幻灯片之后是一个看起来像是占位符的幻灯片,然后演讲者用UIImagePickerController的例子显然演示了最佳实践。 我希望视频可用! :(

    好的,所以......恐怕我的objc-fu不是那么强大。 上述报价中的最后一行我也有点困惑。 我一直在做这方面的工作,我发现看起来像是一篇体面的文章,谈论观察/通知技术的各种方法:
    http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html

    方法#5甚至指示代表作为一种方法! 除....对象一次只能设置一个代表。 所以当我有多个viewcontroller通信时,我该怎么做?

    好的,那就是组建团伙。 我知道我可以通过引用appdelegate中的多个viewcontroller实例轻松地在应用程序委托中完成我的通信方法,但我想以正确的方式执行此类事情。

    请回答以下问题,帮助我“做正确的事情”:

  • 当我尝试在UINavigationController堆栈上推送新的viewcontroller时,应该由谁来执行此操作。 我的代码中哪个类/文件是正确的地方?
  • 当我想影响一些数据块(伊娃的值),我UIViewControllers之一,当我在不同的UIViewController,什么是“正确”的方式做到这一点?
  • 假设我们一次只能在一个对象中设置一个委托,当讲师说“为观察者定义一个通用接口(如委派)”时,实现将会是什么样子。 如果可能的话,伪代码示例会非常有用。

  • 这些都是很好的问题,很高兴看到你正在进行这项研究,并且似乎在学习如何“正确地做”而不是一味地攻击它。

    首先 ,我同意以前的答案,重点是在合适的时候(根据MVC设计模式)将数据放入模型对象的重要性。 通常情况下,您希望避免将状态信息放入控制器中,除非它严格地是“呈现”数据。

    其次 ,请参阅Stanford演示文稿的第10页,了解如何以编程方式将控制器推入导航控制器的示例。 有关如何使用Interface Builder“直观地”执行此操作的示例,请参阅本教程。

    第三 ,也许最重要的是,请注意,如果您在“依赖注入”设计模式的背景下思考它们,那么斯坦福大学演讲中提到的“最佳实践”就会更容易理解。 简而言之,这意味着你的控制器不应该“查找”它需要做的工作(例如,引用一个全局变量)。 相反,您应该始终将这些依赖项“注入”到控制器中(即,通过方法传入它需要的对象)。

    如果你遵循依赖注入模式,你的控制器将是模块化的,可重用的。 如果你想到斯坦福大学的主讲人来自哪里(即,他们的职责是建立可以轻松重复使用的课程),那么可重用性和模块化就成为了重中之重。 他们提到的共享数据的所有最佳实践都是依赖注入的一部分。

    这是我回应的要点。 我将在下面包含一个使用依赖注入模式和控制器的例子,以防有用。

    在View Controller中使用依赖注入的示例

    假设您正在构建列出多本书籍的屏幕。 用户可以选择他/她想要购买的书籍,然后点击“结帐”按钮以转到结账屏幕。

    为了构建这个,你可以创建一个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];
       ...
    }
    

    当苹果公司正在讨论使用委托模式来“沟通备份层次结构”时,他们仍在谈论依赖注入。 在这个例子中,一旦用户选择他/她的书并准备结帐,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
       ...
    }
    

    这种事情总是一个品味的问题。

    话虽如此,我总是喜欢通过模型对象来协调(#2)。 顶级视图控制器加载或创建它需要的模型,并且每个视图控制器在其子控制器中设置属性,以告知它们需要使用哪些模型对象。 通过使用NSNotificationCenter将大多数更改传递回层次结构; 发射通知通常内置于模型本身。

    例如,假设我有一个应用程序与帐户和交易。 我也有一个AccountListController,一个AccountController(它显示一个帐户摘要与“显示所有交易”按钮),一个TransactionListController和一个TransactionController。 AccountListController加载所有帐户的列表并显示它们。 当您点击一个列表项时,它会设置其AccountController的.account属性,并将AccountController推入堆栈。 当您点击“显示所有交易”按钮时,AccountController加载交易列表,将其放入其TransactionListController的.transactions属性中,并将TransactionListController推入堆栈,依此类推。

    例如,如果TransactionController编辑事务,它将在其事务对象中进行更改,然后调用其“保存”方法。 '保存'发送一个TransactionChangedNotification。 任何其他需要在事务更改时自行刷新的控制器都会观察通知并自行更新。 TransactionListController大概会; AccountController和AccountListController可能取决于他们正在尝试做什么。

    对于#1,在我早期的应用程序中,我有一些displayModel:在子控制器中的withNavigationController:方法,它将设置并将控制器推入堆栈。 但是随着我对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有三个部分..模型,视图和控制器..所述问题似乎将其中两个没有很好的理由。 视图和控制器是独立的逻辑块。

    所以...你不想拥有多个视图控制器..

    你想拥有多个视图和一个在它们之间选择的控制器。 (如果你有多个应用程序,你也可以有多个控制器)

    意见不应该作出决定。 控制器应该这样做。 因此,任务和逻辑的分离,以及使您的生活更轻松的方法。

    所以......确保你的观点能够做到这一点,并提出一个很好的数据观点。 让您的控制器决定如何处理数据以及使用哪个视图。

    (当我们谈论数据的时候,我们正在谈论这个模型......一个被存储,访问,修改的好标准方式......另一个我们可以将它们分开并忘记的独立逻辑)

    链接地址: http://www.djcxy.com/p/20689.html

    上一篇: What's the best way to communicate between view controllers?

    下一篇: C singleton look like?