视图控制器之间通信的最佳方式是什么?
作为 Objective-C、cocoa 和 iPhone 开发的新手,我强烈希望充分利用该语言和框架。
我使用的资源之一是斯坦福大学的 CS193P 课堂笔记,它们留在网上。 它包括讲义、作业和示例代码,并且由于该课程是由 Apple 开发人员提供的,所以我绝对认为它是“来自马口铁”。
课程网站:
http://www.stanford.edu/class/cs193p/cgi-bin /index.php
第 08 讲涉及构建一个基于 UINavigationController 的应用程序的作业,该应用程序将多个 UIViewController 推送到 UINavigationController 堆栈上。 这就是 UINavigationController 的工作原理。 这是合乎逻辑的。 然而,幻灯片中有一些关于 UIViewController 之间通信的严厉警告。
我将引用这张严肃的幻灯片:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf
第 16/51 页:
如何不共享数据
- 全局变量或单例
- 这包括您的应用程序委托
- 直接依赖会降低代码的可重用性
- 而且更难调试& 测试
好。 我对此感到沮丧。 不要盲目地将用于视图控制器之间通信的所有方法放入应用程序委托中,并在应用程序委托方法中引用视图控制器实例。 公平的。
再进一步,这张幻灯片告诉我们应该做什么。
第 18/51 页:
数据流最佳实践
- 弄清楚确切需要沟通的内容
- 为视图控制器定义输入参数
- 为了在层次结构上进行备份通信,使用松散耦合
- 为观察者定义通用接口(例如委托)
这张幻灯片后面是一张占位符幻灯片,讲师随后使用 UIImagePickerController 的示例显然演示了最佳实践。 我希望这些视频可用! :(
好吧,所以...我担心我的 objc-fu 不是那么强。我对上面引用中的最后一行也有点困惑。我一直在谷歌上搜索这一点并且我发现一篇看起来不错的文章讨论了观察/通知技术的各种方法:
http://cocoawithlove.com/2008/06/ Five-approaches -to-listening-observing.html
方法 #5 甚至将委托表示为一种方法! 除了......对象一次只能设置一个委托。 那么当我有多个视图控制器通信时,我该怎么办?
好吧,这就是团伙的安排。 我知道我可以通过引用应用程序委托中的多个视图控制器实例轻松地在应用程序委托中执行我的通信方法,但我想以正确的方式执行此类操作。
请回答以下问题,帮助我“做正确的事”:
- 当我尝试在 UINavigationController 堆栈上推送新的视图控制器时,谁应该执行此推送。 我的代码中的哪个类/文件是正确的位置?
- 当我在不同 UIViewController 中时想要影响其中一个 UIViewController 中的某些数据(iVar 的值)时,“正确”的方法是什么?
- 假设我们一次只能在一个对象中设置一个委托,那么当讲师说“为观察者定义一个通用接口(如委托)”时,实现会是什么样子? 如果可能的话,伪代码示例在这里会非常有帮助。
Being new to objective-c, cocoa, and iPhone dev in general, I have a strong desire to get the most out of the language and the frameworks.
One of the resources I'm using is Stanford's CS193P class notes that they have left on the web. It includes lecture notes, assignments and sample code, and since the course was given by Apple dev's, I definitely consider it to be "from the horse's mouth".
Class Website:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php
Lecture 08 is related to an assignment to build a UINavigationController based app that has multiple UIViewControllers pushed onto the UINavigationController stack. That's how the UINavigationController works. That's logical. However, there are some stern warnings in the slide about communicating between your UIViewControllers.
I'm going to quote from this serious of slides:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf
Page 16/51:
How Not To Share Data
- Global Variables or singletons
- This includes your application delegate
- Direct dependencies make your code less reusable
- And more difficult to debug & test
Ok. I'm down with that. Don't blindly toss all your methods that will be used for communicating between the viewcontroller into your app delegate and reference the viewcontroller instances in the app delegate methods. Fair 'nuff.
A bit further on, we get this slide telling us what we should do.
Page 18/51:
Best Practices for Data Flow
- Figure out exactly what needs to be communicated
- Define input parameters for your view controller
- For communicating back up the hierarchy, use loose coupling
- Define a generic interface for observers (like delegation)
This slide is then followed by what appears to be a place holder slide where the lecturer then apparently demonstrates the best practices using an example with the UIImagePickerController. I wish the videos were available! :(
Ok, so... I'm afraid my objc-fu is not so strong. I'm also a bit confused by the final line in the above quote. I've been doing my fair share of googling about this and I found what appears to be a decent article talking about the various methods of Observing/Notification techniques:
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html
Method #5 even indicates delegates as an method! Except.... objects can only set one delegate at a time. So when I have multiple viewcontroller communication, what am I to do?
Ok, that's the set up gang. I know I can easily do my communication methods in the app delegate by reference's the multiple viewcontroller instances in my appdelegate but I want to do this sort of thing the right way.
Please help me "do the right thing" by answering the following questions:
- When I am trying to push a new viewcontroller on the UINavigationController stack, who should be doing this push. Which class/file in my code is the correct place?
- When I want to affect some piece of data (value of an iVar) in one of my UIViewControllers when I am in a different UIViewController, what is the "right" way to do this?
- Give that we can only have one delegate set at a time in an object, what would the implementation look like for when the lecturer says "Define a generic interface for observers (like delegation)". A pseudocode example would be awfully helpful here if possible.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这些都是很好的问题,很高兴看到您正在进行这项研究,并且似乎关心学习如何“正确地做事”,而不是仅仅将其拼凑在一起。
首先,我同意之前的答案,这些答案重点关注在适当的时候将数据放入模型对象中的重要性(根据 MVC 设计模式)。 通常您希望避免将状态信息放入控制器中,除非它是严格的“表示”数据。
第二,请参阅斯坦福大学演示文稿第 10 页,了解如何以编程方式将控制器推送到导航控制器上的示例。 有关如何使用 Interface Builder “直观地”执行此操作的示例,请查看 本教程。
第三,也许是最重要的一点,请注意,如果您在“依赖关系”的背景下考虑斯坦福大学演讲中提到的“最佳实践”,那么它们会更容易理解。注入”设计模式。 简而言之,这意味着您的控制器不应该“查找”完成其工作所需的对象(例如,引用全局变量)。 相反,您应该始终将这些依赖项“注入”到控制器中(即通过方法传入它所需的对象)。
如果遵循依赖注入模式,您的控制器将是模块化且可重用的。 如果你考虑一下斯坦福大学的演示者来自哪里(即,作为苹果员工,他们的工作是构建可以轻松重用的课程),可重用性和模块化是重中之重。 他们提到的所有共享数据的最佳实践都是依赖注入的一部分。
这就是我回应的要点。 我将在下面提供一个使用依赖注入模式与控制器的示例,以防有帮助。
通过视图控制器使用依赖注入的示例
假设您正在构建一个列出多本书的屏幕。 用户可以选择他/她想要购买的书籍,然后点击“结帐”按钮进入结帐屏幕。
要构建它,您可以创建一个 BookPickerViewController 类来控制和显示 GUI/视图对象。 它从哪里获取所有图书数据? 假设它依赖于 BookWarehouse 对象。 所以现在你的控制器基本上是在模型对象(BookWarehouse)和 GUI/视图对象之间代理数据。 换句话说,BookPickerViewController 依赖于 BookWarehouse 对象。
不要这样做:
相反,依赖项应该像这样注入:
当苹果公司的人谈论使用委托模式来“通信备份层次结构”时,他们仍然在谈论依赖项注入。 在此示例中,一旦用户选择了他/她的书籍并准备好结账,BookPickerViewController 应该做什么? 嗯,这并不是它真正的工作。 它应该将工作委托给其他对象,这意味着它依赖于另一个对象。 因此,我们可以修改 BookPickerViewController init 方法,如下所示:
所有这一切的最终结果是,您可以给我您的 BookPickerViewController 类(以及相关的 GUI/视图对象),我可以轻松地在我自己的应用程序中使用它,假设 BookWarehouse 和 CheckoutController 是我可以实现的通用接口(即协议):
最后,您的 BookPickerController 不仅可重用,而且更易于测试。
These are good questions, and its great to see that you're doing this research and seem concerned with learning how to "do it right" instead of just hacking it together.
First, I agree with the previous answers which focus on the importance of putting data in model objects when appropriate (per the MVC design pattern). Usually you want to avoid putting state information inside a controller, unless it's strictly "presentation" data.
Second, see page 10 of the Stanford presentation for an example of how to programmatically push a controller onto the navigation controller. For an example of how to do this "visually" using Interface Builder, take a look at this tutorial.
Third, and perhaps most importantly, note that the "best practices" mentioned in the Stanford presentation are much easier to understand if you think about them in the context of the "dependency injection" design pattern. In a nutshell, this means that your controller shouldn't "look up" the objects it needs to do its job (e.g., reference a global variable). Instead, you should always "inject" those dependencies into the controller (i.e., pass in the objects it needs via methods).
If you follow the dependency injection pattern, your controller will be modular and reusable. And if you think about where the Stanford presenters are coming from (i.e., as Apple employees their job is to build classes that can easily be reused), reusability and modularity are high priorities. All of the best practices they mention for sharing data are part of dependency injection.
That's the gist of my response. I'll include an example of using the dependency injection pattern with a controller below in case it's helpful.
Example of Using Dependency Injection with a View Controller
Let's say you're building a screen in which several books are listed. The user can pick books he/she wants to buy, and then tap a "checkout" button to go to the checkout screen.
To build this, you might create a BookPickerViewController class that controlls and displays the GUI/view objects. Where will it get all the book data? Let's say it depends on a BookWarehouse object for that. So now your controller is basically brokering data between a model object (BookWarehouse) and the GUI/view objects. In other words, BookPickerViewController DEPENDS on the BookWarehouse object.
Don't do this:
Instead, the dependencies should be injected like this:
When the Apple guys are talking about using the delegation pattern to "communicate back up the hierarchy," they're still talking about dependency injection. In this example, what should the BookPickerViewController do once the user has picked his/her books and is ready to check out? Well, that's not really its job. It should DELEGATE that work to some other object, which means that it DEPENDS on another object. So we might modify our BookPickerViewController init method as follows:
The net result of all this is that you can give me your BookPickerViewController class (and related GUI/view objects) and I can easily use it in my own application, assuming BookWarehouse and CheckoutController are generic interfaces (i.e., protocols) that I can implement:
Finally, not only is your BookPickerController reusable but also easier to test.
这种事情总是一个品味问题。
话虽如此,我总是更喜欢通过模型对象进行协调(#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,来编辑交易的两个属性。 编辑者实际上不应保存正在编辑的交易,因为用户可能决定放弃该交易。 因此,他们都将其父控制器作为委托,并调用其方法来说明是否更改了任何内容。
现在是 TransactionController 中的一些方法:
需要注意的是,我们定义了一个通用协议,编辑者可以使用该协议与其所属的控制器进行通信。 通过这样做,我们可以在应用程序的其他部分重用编辑器。 (也许帐户也可以有注释。)当然,EditorDelegate 协议可以包含不止一种方法; 在这种情况下,这是唯一必要的。
This sort of thing is always a matter of taste.
Having said that, I always prefer to do my coordination (#2) via model objects. The top-level view controller loads or creates the models it needs, and each view controller sets properties in its child controllers to tell them which model objects they need to work with. Most changes are communicated back up the hierarchy by using NSNotificationCenter; firing the notifications is usually built in to the model itself.
For example, suppose I have an app with Accounts and Transactions. I also have an AccountListController, an AccountController (which displays an account summary with a "show all transactions" button), a TransactionListController, and a TransactionController. AccountListController loads a list of all accounts and displays them. When you tap on a list item, it sets the .account property of its AccountController and pushes the AccountController onto the stack. When you tap the "show all transactions" button, AccountController loads the transaction list, puts it in its TransactionListController's .transactions property, and pushes the TransactionListController onto the stack, and so on.
If, say, TransactionController edits the transaction, it makes the change in its transaction object and then calls its 'save' method. 'save' sends a TransactionChangedNotification. Any other controller that needs to refresh itself when the transaction changes would observe the notification and update itself. TransactionListController presumably would; AccountController and AccountListController might, depending on what they were trying to do.
For #1, in my early apps I had some sort of displayModel:withNavigationController: method in the child controller that would set things up and push the controller onto the stack. But as I've become more comfortable with the SDK, I've drifted away from that, and now I usually have the parent push the child.
For #3, consider this example. Here we are using two controllers, AmountEditor and TextEditor, to edit two properties of a Transaction. The Editors should not actually save the transaction being edited, since the user could decide to abandon the transaction. So instead they both take their parent controller as a delegate and call a method on it saying if they've changed anything.
And now a few methods from TransactionController:
The thing to notice is that we've defined a generic protocol which Editors may use to communicate with their owning controller. By doing so, we can reuse the Editors in another part of the application. (Perhaps Accounts can have notes, too.) Of course, the EditorDelegate protocol could contain more than one method; in this case that's the only one necessary.
我明白你的问题了。
发生的事情是有人对 MVC 架构的想法感到困惑。
MVC 由三个部分组成:模型、视图和控制器。所提到的问题似乎没有充分的理由将其中两个部分组合在一起。 视图和控制器是独立的逻辑部分。
所以......你不想有多个视图控制器......
你想要有多个视图,以及一个在它们之间进行选择的控制器。 (如果您有多个应用程序,您也可以有多个控制器)
视图不应该做出决定。 控制器应该这样做。 因此,任务、逻辑以及让你的生活更轻松的方法是分离的。
所以..确保你的视图能够做到这一点,提供一个很好的数据视图。 让您的控制器决定如何处理数据以及使用哪个视图。
(当我们谈论数据时,我们谈论的是模型......一种存储、访问、修改的良好标准方式......我们可以将其打包并忘记的另一个单独的逻辑部分)
I see your problem..
What has happened is that someone has confused idea of MVC architecture.
MVC has three parts.. models, views, and controllers.. The stated problem seems to have combined two of them for no good reason. views and controllers are seperate pieces of logic.
so... you do not want to have multiple view-controllers..
you want to have multiple views, and a controller that chooses between them. (you could also have multiple controllers, if you have multiple applications )
views should NOT be making decisions. The controller(s) should do that. Hence the seperation of tasks, and logic, and ways of making your life easier.
So.. make sure your view just does that, puts out a nice veiw of the data. let your controller decide what to do with the data, and which view to use.
(and when we talk about data, we are talking about the model... a nice standard way of being storred, accessed, modified.. another separate piece of logic that we can parcel away and forget about)
假设有两个类A和B。
类A的实例是
A aInstance;
A 类创建 B 类的实例,作为
B bInstance;
在 B 类的逻辑中,您需要在某个地方通信或触发 A 类的方法。
1) 错误的方式
您可以将 aInstance 传递给 bInstance。
现在从 bInstance 中的所需位置调用所需方法 [aInstance methodname]。
这本来可以满足您的目的,但是释放时会导致内存被锁定而不是释放。
如何?
当你将aInstance传递给bInstance时,我们将aInstance的retainCount增加了1。
当释放 bInstance 时,我们会遇到内存阻塞,因为 bInstance 永远无法将 aInstance 的保留计数变为 0,因为 bInstance 本身就是 aInstance 的对象。
进一步,由于aInstance被卡住,bInstance的内存也会被卡住(泄漏)。
因此,即使稍后释放 aInstance 本身,它的内存也会被阻塞,因为 bInstance 无法释放,而 bInstance 是 aInstance 的类变量。
2)正确的方法
通过将aInstance定义为bInstance的delegate,就不会出现aInstance的retaincount变化或内存纠缠。
bInstance 将能够自由调用 aInstance 中的委托方法。
bInstance 释放时,所有变量将由其自己创建并释放
在aInstance的释放中,由于bInstance中没有aInstance的纠缠,因此它会被干净地释放。
Suppose there are two classes A and B.
instance of class A is
A aInstance;
class A makes and instance of class B, as
B bInstance;
And in your logic of class B, somewhere you are required to communicate or trigger a method of class A.
1) Wrong way
You could pass the aInstance to bInstance.
now place the call of the desired method [aInstance methodname] from the desired location in bInstance.
This would have served your purpose, but while release would have led to a memory being locked and not freed.
How?
When you passed the aInstance to bInstance, we increased the retaincount of aInstance by 1.
When deallocating bInstance, we will have memory blocked because aInstance can never be brought to 0 retaincount by bInstance reason being that bInstance itself is an object of aInstance.
Further, because of aInstance being stuck, the memory of bInstance will also be stuck(leaked).
So even after deallocating aInstance itself when its time comes later on, its memory too will be blocked because bInstance cant be freed and bInstance is a class variable of aInstance.
2)Right way
By defining aInstance as the delegate of bInstance, there will be no retaincount change or memory entanglement of aInstance.
bInstance will be able to freely invoke the delegate methods lying in the aInstance.
On bInstance's deallocation, all the variables will be its own created and will be released
On aInstance's deallocation, as there is no entanglement of aInstance in bInstance, it will be released cleanly.