为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

iOS
慕的地6264312 2019-08-03 07:03:21
在视图控制器之间进行通信的最佳方式是什么?作为目标c、可可和iPhonedev的新手,我强烈希望充分利用语言和框架。我正在使用的资源之一是斯坦福大学的CS193P课程,这是他们在网络上留下的。它包括课堂讲稿、作业和示例代码,而且由于这门课程是由Appledev提供的,所以我肯定认为它是“从马嘴里来的”。班级网站:http:/www.stanford.edu/class/cs193p/cgi-bin/index.php第08课与构建一个基于UINavigationController的应用程序有关,该应用程序有多个UIViewController被推入UINavigationController堆栈。UINavigationController就是这样工作的。这是合乎逻辑的。但是,幻灯片中有一些关于UIViewController之间通信的严厉警告。我要引用这一系列的幻灯片:http:/cs193p.stanford.edu/下载/08-NavigationTabBarControllers.pdf第16/51页:如何不共享数据全局变量或单变量这包括你的申请委托直接依赖关系降低了代码的可重用性。更难调试和测试好的。我受够了。不要盲目地将用于在视图控制器之间通信的所有方法抛到应用程序委托中,并在app委托方法中引用viewController实例。还不错。再往前看,这张幻灯片告诉我们应做。第18/51页:数据流的最佳实践弄清楚一点儿没错需要传达什么定义输入参数用于视图控制器为了恢复层次结构,使用松耦合为观察者定义一个通用接口(如委托)这张幻灯片之后是一张看上去像是保持式幻灯片的幻灯片,讲师随后用UIImagePickerController的例子演示了最佳实践。我希望录影带能提供!好吧那么.。恐怕我的objc-fu没那么强壮。我也有点困惑,最后一行在上面的报价。我一直在谷歌上搜索这方面的内容,我找到了一篇似乎不错的文章,讨论了各种观察/通知技术的方法:http:/cocoawithlove.com/2008/06/5方法#5甚至指示委托为一种方法!除了.。对象一次只能设置一个委托。那么,当我有多个视图控制器通信时,我该怎么办呢?好吧,那是密谋团伙。我知道我可以很容易地在app委托中使用我的通信方法,通过引用我的appagent中的多个视图控制器实例,但是我想做这样的事情右(边),正确的方式,道路。请回答以下问题,帮助我“做正确的事情”:当我试图在UINavigationController堆栈上推送一个新的视图控制器时,谁应该做这件事。哪一个类/文件在我的代码中是正确的位置吗?当我想影响我的UIViewController中的某个数据(一个IPAR的值)时,我在异类UIViewController,做这件事的“正确”方法是什么?假设我们一次只能在一个对象中设置一个委托,那么当讲师说的时候,实现是什么样子的呢?“为观察员定义通用接口(如委托)”..如果可能的话,伪代码示例在这里非常有用。
查看完整描述

3 回答

?
慕标5832272

TA贡献1966条经验 获得超4个赞

这些都是很好的问题,很高兴看到你正在做这项研究,并且似乎关心的是如何“做好”,而不是仅仅把它整合在一起。

第一,我同意前面的答案,这些答案侧重于在适当的时候将数据放在模型对象中的重要性(根据MVC设计模式)。通常,您希望避免将状态信息放入控制器中,除非它是严格的“表示”数据。

第二,有关如何以编程方式将控制器推送到导航控制器的示例,请参阅斯坦福演示文稿第10页。有关如何使用InterfaceBuilder进行“可视化”操作的示例,请参阅本教程.

第三也许最重要的是,请注意,如果您在“依赖注入”设计模式中考虑到“最佳实践”,那么在斯坦福演示中提到的“最佳实践”要容易得多。简而言之,这意味着您的控制器不应该“查找”它执行其工作所需的对象(例如,引用全局变量)。相反,您应该始终将这些依赖项“注入”到控制器中(即,通过方法传递它需要的对象)。

如果遵循依赖注入模式,则控制器将是模块化和可重用的。如果你想一想斯坦福的演讲者们来自哪里(比如,作为苹果员工,他们的工作就是构建易于重用的类),可重用性和模块化是高度优先考虑的问题。他们提到的共享数据的所有最佳实践都是依赖注入的一部分。

这就是我回答的要点。我将包括一个示例,说明如何在下面的控制器中使用依赖注入模式,以防有帮助。

在视图控制器中使用依赖项注入的示例

假设您正在构建一个屏幕,其中列出了几本书。用户可以选择他/她想买的书,然后点击“结账”按钮进入结账屏幕。

为此,您可以创建一个BookPickerViewController类来控制和显示GUI/View对象。它将从哪里得到所有的书籍数据?假设它依赖于一个BookWarehouse对象。现在,您的控制器基本上是在模型对象(BookWarehouse)和GUI/View对象之间代理数据。换句话说,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应该做些什么?这不是它的工作。它应该将工作委托给其他对象,这意味着它依赖于另一个对象。因此,我们可以修改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
   ...}




查看完整回答
反对 回复 2019-08-05
?
慕雪6442864

TA贡献1812条经验 获得超5个赞

这类事情总是品位问题。

话虽如此,我总是更喜欢通过模型对象进行协调(#2)。顶层视图控制器加载或创建所需的模型,每个视图控制器在其子控制器中设置属性,以告诉它们需要使用哪些模型对象。大多数更改都是通过使用NSNotificationCenter在层次结构上进行通信的;触发通知通常是内置到模型本身的。

例如,假设我有一个带有帐户和事务的应用程序。我还有一个AccountListController、一个AccountController(它用一个“显示所有事务”按钮显示帐户摘要)、一个TransactionListController和一个TransactionController。AccountListController加载所有帐户的列表并显示它们。当您点击列表项时,它会设置其AccountController的.Account属性,并将AccountController推到堆栈上。当您点击“显示所有事务”按钮时,AccountController加载事务列表,将其放入其TransactionListController的.Transes属性中,并将TransactionListController推送到堆栈上,依此类推。

例如,如果TransactionController编辑了事务,它就会对其事务对象进行更改,然后调用它的‘Save’方法。“保存”发送一个TransactionChangedNotification。当事务更改时,任何其他需要刷新自身的控制器都会观察通知并更新自身。据推测,TransactionListController会这样做;AccountController和AccountListController可能会这样做,这取决于它们试图做什么。

对于#1,在我早期的应用程序中,我有某种显示模型:With NavigationController:方法在子控制器中,它会设置东西并将控制器推到堆栈上。但是,随着我对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协议可以包含多个方法;在这种情况下,这是唯一必要的方法。




查看完整回答
反对 回复 2019-08-05
  • 3 回答
  • 0 关注
  • 439 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信