如何在iOS视图控制器中注入依赖项?

10

我的视图控制器需要向一些模型对象发送消息。我如何在视图控制器内获取这些模型对象的引用?

这些模型对象是“单例”(系统中应该只有一个副本),并且由多个视图控制器使用。因此,我不能在每个视图控制器的init方法中实例化它们。

我不能使用构造函数注入,因为运行时会选择用于创建视图控制器的init方法。

我无法使用“setter注入”,因为在任何时候(我所知道的)都没有同时拥有对新构建的视图控制器和“单例”模型对象的引用。

我不希望将模型对象变成真正的单例,并从视图控制器调用它们的静态方法来检索单例实例,因为这会影响可测试性(将模型对象作为AppDelegate的属性与此基本相同)。

我正在使用带Storyboards的iOS 6。


3
你是如何创建单例模式的?如果你遵循了苹果的示例,那么你可以在任何时候检索共享实例而不创建新实例。 - sosborn
1
我知道如何创建单例并且它能够正常工作,但是这对于可测试性来说不利,因为在测试期间无法将模拟对象注入到真实对象的位置。 - Robert Atkins
为什么在提供单例模型的情况下,设值注入无法正常工作? - Jon Reid
从哪里获取?我在哪个地方既有对单例模型的引用,又有对视图控制器的引用(该视图控制器在应用程序启动时由运行时实例化)? - Robert Atkins
1
这就是为什么我从来不喜欢Storyboards的原因之一... - de.
很遗憾,四年后我们仍然没有一种明智的官方方法在Storyboard中传递视图控制器之间的数据。prepareForSegue:这个替代品纯粹是脑损伤。 - zoul
4个回答

8
我刚刚处理了同样的问题。由于我正在使用故事板,因此我不会实例化我的UIViewControllers,所以我无法使用"构造函数注入"。我必须使用setter注入。
我的应用程序根视图是一个UITabViewController。假设它有两个UINavigationController,第一个具有AControllerView,第二个具有BControllerView。在AppDelegate.applicationDidFinishLaunchingWithOptions中,您可以通过以下方式检索根控制器: UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController; 然后,您可以遍历控制器:
NSArray* viewControllers = [tabBarController viewControllers];
for (UIViewController *viewController in viewControllers) {
    UINavigationController *navigationController = (UINavigationController*) viewController;
    UIViewController *viewController = navigationController.topViewController;
    if ([viewController isKindOfClass: [AControllerView class]]) {
        AControllerView *a = (AControllerView*) viewController;
        // Inject your stuff
    }
    if ([viewController isKindOfClass: [BControllerView class]]) {
        BControllerView *b = (BControllerView*) viewController;
        // Inject your stuff
    }
}

希望这有所帮助。

2
我们将称之为“构建定制IOC容器”的方法。如果您选择这种方式,很快就会发现最好使用GitHub上受Spring启发的Objective-C IOC容器。我对您到达那里需要多长时间感兴趣并期待反馈。 - Robert Atkins
嗯,我大部分是Java开发者,我想要学习正确的Objective C和iOS编程方式,我不想以其他语言的方式来编写iOS程序!有没有更好的方法在Core Data上下文中传递?这确实让我想起了Hibernate Entity Manager注入。 - juanignaciosl
那不是批评!我和你处于同样的情况;似乎在Obj-C中IOC并不是一个“东西”,以至于每当我询问Obj-C开发人员如何完成Java开发人员所做的IOC工作时,感觉我们在说不同的事情。 - Robert Atkins
1
嘿,我并不想把它看作是一种批评 :). 只是一个免责声明。我的一些朋友开玩笑说过“Java开发者的方式”,但我和你描述的情况一样。这不是“我错过了Seam/Spring”的问题。我错过了解耦和可测试性 :/。不知怎么的,我觉得IoC框架不是我需要的,但另一种选择也感觉很奇怪,不是吗? - juanignaciosl

5

为什么不使用NSNotificationCenter

NSNotificationCenter对象(或简称通知中心)提供了程序内广播信息的机制。一个NSNotificationCenter对象本质上是一个通知分发表。

您可以在单例或常规通知中添加通知观察者,当您需要发送消息时,只需发布正确的通知即可。观察者将管理操作。

有关NSNotificationCenter的更多详细信息


视图控制器想要发送的不是广播“通知”,而是带有返回值的方法调用,因此我认为观察者并不合适。 - Robert Atkins
“广播”只是一种选择,你可以将观察者添加到特定的对象上,一对一也可以,而且你还可以通过它返回值。这非常方便。 - Kjuly
你能否添加一个示例,说明需要模型的VC如何获取对它的引用?如果你的解决方案意味着观察每个模型对象的一个通知,那看起来相当复杂。 - zoul
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Kjuly
谢谢回复!问题是,使用传统的setter或构造函数注入时,需要某些依赖项的VC中没有任何开销。它只需公开一个构造函数参数或依赖项的setter,并从外部接收它,就可以了。使用通知中心解决方案,它将需要订阅每个依赖项的通知?这感觉相当过度。 - zoul
@zoul 正确,但你不仅可以选择接收所有带有名称的通知,还可以选择只接收特定目标的通知。当您通过 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject; 添加观察者时,可以设置要观察的目标,正如文档所述:“观察者想要接收通知的对象;也就是说,只有由这个发送方发送的通知才会传递给观察者。”它有点像代理,但更独立。 :) - Kjuly

2
这不是关于获取视图控制器对象的引用吗?如果您正在使用故事板,则窗口的rootViewController或使用的转场将为您提供正确的视图控制器对象。
例如: 应用程序启动时,视图控制器的实例为 self.window.rootViewController 当您使用转场在场景(视图控制器)之间进行转换时: [segue destinationViewController][segue sourceViewController] 如果您正在使用xib,甚至可以使用来自接口构建器的外部对象(代理对象)来提供模型对象。唯一的问题是您必须自己处理nib实例化。

1
理想情况下,第三方开发人员将被允许在使用storyboards时使用自己的构造函数/初始化程序。
在那之前,您可以使用setter /属性注入和中介模式,特别是因为您习惯了最佳实践和松耦合。
我在这里写过:http://cocoapatterns.com/passing-data-between-view-controllers/

在您链接页面上的示例中,我不确定ViewControllerA和ViewControllerB如何引用您的中介对象? 中介是否有一个sharedInstance类方法来返回其单例? 如果是这样,我觉得这是一种渐进式改进(超过VC自己管理对每个资源的引用),但我不认为它能够明确解决问题 - 实际上,当中介提供一个VC需要但另一个不需要的属性访问时,它会引入一些不必要的耦合。 - Robert Atkins
@RoberAtkins 所有的VC都持有一个中介者的引用,这个引用是由中介者本身在segues的一部分中注入给它们的。初始的VC从组合根(在iOS的情况下是AppDelegate或者你创建对象图的任何地方)获取中介者。可以通过一个协议(例如命名为Transitionable)来更加清晰地实现这一点,所有的VC都符合该协议。如果您喜欢,中介者也可以是单例。我个人不这样做,尽管我可以理解为什么有人会在这种情况下主张这样做。 - joakim
此外,正如您所说,它可能会引入一些不必要的耦合,但与“每个人都为自己”方法相比,它也有助于保持总体耦合度低,后者很容易导致耦合爆炸。当然,该模式的好处在于大型项目上表现出色,但是在2-4个场景的应用程序中引入该模式/开销显然是过度的。主要优点是通过限制需要在中介器中进行转换时需要进行的更改来帮助维护和替换VCs。 - joakim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接