objective-c 在视图控制器之间进行通信的最佳方式是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/569940/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What's the best way to communicate between view controllers?
提问by Clint Harris
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.
作为 Objective-c、cocoa 和 iPhone 开发的新手,我强烈希望充分利用语言和框架。
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".
我使用的资源之一是他们留在网上的斯坦福 CS193P 课堂笔记。它包括讲义、作业和示例代码,而且由于该课程是由 Apple 开发人员提供的,我绝对认为它是“出自马口”。
Class Website:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php
班级网址: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.
Lecture 08 与构建基于 UINavigationController 的应用程序的任务有关,该应用程序将多个 UIViewControllers 推送到 UINavigationController 堆栈上。这就是 UINavigationController 的工作方式。这是合乎逻辑的。但是,幻灯片中有一些关于 UIViewControllers 之间通信的严厉警告。
I'm going to quote from this serious of slides:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf
我将引用这一系列的幻灯片:http:
//cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf
Page 16/51:
第 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.
好的。我不服。不要盲目地将用于视图控制器之间通信的所有方法都扔到应用程序委托中,并在应用程序委托方法中引用视图控制器实例。公平'nuff。
A bit further on, we get this slide telling us what we shoulddo.
再往前走一点,我们会看到这张幻灯片告诉我们应该做什么。
Page 18/51:
第 18/51 页:
Best Practices for Data Flow
- Figure out exactlywhat needs to be communicated
- Define input parametersfor 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! :(
这张幻灯片之后是一个似乎是占位符幻灯片的内容,然后讲师显然使用 UIImagePickerController 的示例演示了最佳实践。我希望视频可用!:(
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
好吧,所以……恐怕我的 objc-fu 没有那么强。我也对上面引用的最后一行有点困惑。我一直在做我相当多的谷歌搜索,我发现一篇看起来不错的文章谈论了观察/通知技术的各种方法: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?
方法 #5 甚至将委托表示为一种方法!除了.... 对象一次只能设置一个委托。那么当我有多个视图控制器通信时,我该怎么办?
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 rightway.
好的,这就是成立帮派。我知道我可以通过引用我的 appdelegate 中的多个 viewcontroller 实例轻松地在应用程序委托中执行我的通信方法,但我想以正确的方式做这种事情。
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, whoshould be doing this push. Whichclass/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 differentUIViewController, 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.
- 当我尝试在 UINavigationController 堆栈上推送一个新的视图控制器时,应该由谁执行此推送。 我的代码中的哪个类/文件是正确的位置?
- 当我想影响一些数据块(伊娃的值),我UIViewControllers之一,当我在不同的UIViewController,什么是“正确”的方式做到这一点?
- 假设我们一次只能在一个对象中设置一个委托,那么当讲师说“为观察者定义一个通用接口(如委托)”时,实现会是什么样子。如果可能的话,伪代码示例在这里会非常有用。
回答by Clint Harris
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.
首先,我同意之前的答案,这些答案侧重于在适当的时候(根据 MVC 设计模式)将数据放入模型对象的重要性。通常你想避免将状态信息放在控制器中,除非它是严格的“表示”数据。
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.
其次,有关如何以编程方式将控制器推送到导航控制器上的示例,请参见斯坦福演示文稿的第 10 页。有关如何使用 Interface Builder“以视觉方式”执行此操作的示例,请查看本教程。
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.
如果您遵循依赖注入模式,您的控制器将是模块化的且可重用的。如果您考虑斯坦福演示者的来源(即,作为 Apple 员工,他们的工作是构建可以轻松重用的类),可重用性和模块化是重中之重。他们提到的所有共享数据的最佳实践都是依赖注入的一部分。
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.
要构建它,您可以创建一个 BookPickerViewController 类来控制和显示 GUI/视图对象。它将从哪里获得所有书籍数据?假设它取决于 BookWarehouse 对象。所以现在您的控制器基本上是在模型对象 (BookWarehouse) 和 GUI/视图对象之间代理数据。换句话说,BookPickerViewController 依赖于 BookWarehouse 对象。
Don't do this:
不要这样做:
@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];
...
}
Instead, the dependencies should be injected like this:
相反,应该像这样注入依赖项:
@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];
...
}
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:
当苹果公司的人在谈论使用委托模式来“沟通备份层次结构”时,他们仍然在谈论依赖注入。在这个例子中,一旦用户选择了他/她的书并准备结帐,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];
...
}
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:
所有这一切的最终结果是,您可以给我您的 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];
}
Finally, not only is your BookPickerController reusable but also easier to test.
最后,您的 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
...
}
回答by Brent Royal-Gordon
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.
话虽如此,我总是更喜欢通过模型对象进行协调(#2)。顶层视图控制器加载或创建它需要的模型,每个视图控制器在它的子控制器中设置属性来告诉他们他们需要使用哪些模型对象。大多数更改通过使用 NSNotificationCenter 传递到层次结构;触发通知通常内置于模型本身。
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.
例如,假设我有一个包含帐户和交易的应用程序。我还有一个 AccountListController、一个 AccountController(它显示一个带有“显示所有交易”按钮的帐户摘要)、一个 TransactionListController 和一个 TransactionController。AccountListController 加载所有帐户的列表并显示它们。当您点击一个列表项时,它会设置其 AccountController 的 .account 属性并将 AccountController 推送到堆栈上。当您点击“显示所有交易”按钮时,AccountController 加载交易列表,将其放入其 TransactionListController 的 .transactions 属性中,并将 TransactionListController 推入堆栈,依此类推。
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.
例如,如果 TransactionController 编辑事务,它会在其事务对象中进行更改,然后调用其“保存”方法。“保存”发送一个 TransactionChangedNotification。任何其他需要在事务更改时自行刷新的控制器都会观察通知并自行更新。TransactionListController 大概会;AccountController 和 AccountListController 可能,这取决于他们试图做什么。
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.
对于#1,在我早期的应用程序中,我在子控制器中使用了某种 displayModel:withNavigationController: 方法,可以进行设置并将控制器推送到堆栈上。但是随着我对 SDK 越来越熟悉,我已经逐渐远离它,现在我通常让父母推动孩子。
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.
对于#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
And now a few methods from TransactionController:
现在有一些来自 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];
}
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.
需要注意的是,我们已经定义了一个通用协议,编辑器可以使用该协议与他们拥有的控制器进行通信。通过这样做,我们可以在应用程序的另一部分重用编辑器。(也许 Accounts 也可以有注释。)当然,EditorDelegate 协议可以包含多个方法;在这种情况下,这是唯一需要的。
回答by Bingy
I see your problem..
我看到你的问题..
What has happened is that someone has confused idea of MVC architecture.
发生的事情是有人混淆了 MVC 架构的概念。
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.
MVC 包含三个部分.. 模型、视图和控制器.. 所述问题似乎无缘无故地将其中两个组合在一起。视图和控制器是独立的逻辑部分。
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)
(当我们谈论数据时,我们谈论的是模型……一种很好的存储、访问、修改的标准方式……我们可以打包并忘记的另一个单独的逻辑部分)
回答by rd_
Suppose there are two classes A and B.
假设有两个类 A 和 B。
instance of class A is
A类的实例是
A aInstance;
一个实例;
class A makes and instance of class B, as
A 类是 B 类的实例,如
B bInstance;
B 实例;
And in your logic of class B, somewhere you are required to communicate or trigger a method of class A.
在 B 类的逻辑中,您需要在某处进行通信或触发 A 类的方法。
1) Wrong way
1)错误的方式
You could pass the aInstance to bInstance. now place the call of the desired method [aInstance methodname] from the desired location in bInstance.
您可以将 aInstance 传递给 bInstance。现在从 bInstance 中的所需位置调用所需的方法 [aInstance methodname]。
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.
当您将 aInstance 传递给 bInstance 时,我们将 aInstance 的 retaincount 增加了 1。当释放 bInstance 时,我们将阻塞内存,因为 bInstance 永远无法将 aInstance 置为 0 retaincount,原因是 bInstance 本身是 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.
进一步的,因为aInstance被卡住,bInstance的内存也会被卡住(泄露)。因此,即使在稍后释放 aInstance 本身之后,它的内存也会被阻塞,因为 bInstance 无法释放并且 bInstance 是 aInstance 的类变量。
2)Right way
2)正确的方式
By defining aInstance as the delegate of bInstance, there will be no retaincount change or memory entanglement of aInstance.
通过将aInstance定义为bInstance的delegate,aInstance不会有retaincount变化或内存纠缠。
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.
bInstance 将能够自由调用位于 aInstance 中的委托方法。bInstance的deallocation时,所有的变量都是自己创建的,会被释放。

