如何将依赖项注入 iOS 视图控制器?
Posted
技术标签:
【中文标题】如何将依赖项注入 iOS 视图控制器?【英文标题】:How do I inject dependencies into an iOS view controller? 【发布时间】:2012-11-01 00:35:51 【问题描述】:我的视图控制器需要向几个模型对象发送消息。如何在视图控制器中获取对这些模型对象的引用?
这些模型对象是“单例”(因为系统中一次应该只有一个它们的副本)并且它们被多个视图控制器使用。所以我不能在每个视图控制器的init方法中实例化它们。
我不能使用构造函数注入,因为运行时会选择用于创建视图控制器的 init 方法。
我不能使用“setter injection”,因为在任何时候(据我所知)我都没有对新构造的视图控制器的引用和对“singleton”模型对象的引用。
我不想要将模型对象转换为适当的单例,并从视图控制器调用它们的静态方法来检索单例实例,因为这是可测试性的问题。 (将模型对象作为 AppDelegate 上的属性与这样做基本相同。)
我正在使用带有 Storyboards 的 ios 6。
【问题讨论】:
你是如何创建你的单例模式的?如果您遵循 Apple 示例,那么您可以随时检索共享实例而无需创建新实例。 我知道如何创建一个单例并且我知道它可以工作,但它不利于可测试性,因为在测试期间您不能注入模拟来代替真实对象。 为什么不提供单例模型的setter注入工作? 从哪里来的?我在什么地方有对单例模型的引用和对视图控制器的引用(在应用程序启动时已由运行时实例化)? 这就是为什么我从来没有让自己喜欢 Storyboards 的原因之一...... 【参考方案1】:我刚刚处理了同样的问题。因为我使用的是故事板,所以我没有实例化我的UIViewControllers
,所以我不能使用“构造函数注入”。我必须使用 setter 注入辞职。
我的应用根目录是UITabViewController
。假设它有两个UINavigationController
s,第一个是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
希望对您有所帮助。
【讨论】:
我们将其称为“构建自定义 IOC 容器”方法。如果您走这条路,您很快就会到达可以使用 GitHub 上受 Spring 启发的 Objective-C IOC 容器之一的地方。我很想收到有关您到达那里需要多长时间的反馈。 好吧,我主要是一名 Java 开发人员,我想以正确的方式学习 Objective C 和 iOS,我不想像使用其他语言一样对 iOS 进行编程!有没有更好的方法来传递核心数据上下文?它当然让我想起了 Hibernate Entity Manager 注入。 这不是批评!我和你的情况一样;似乎 IOC 不是 Obj-C 中的“东西”,以至于每当我向 Obj-C 开发人员询问他们如何完成 IOC 为 Java 开发人员所做的事情时,感觉就像我们在谈论不同的目的. 嘿,我不想做出反应,因为这是批评:)。只是一个免责声明。我的一些朋友曾拿“Java 开发者方式”开玩笑,但我的情况与您描述的相同。这不是“我想念Seam/Spring”的问题。我想念解耦和可测试性:/。不知何故,我觉得这里不需要 IoC 框架,但替代方案也感觉很奇怪,不是吗?【参考方案2】:为什么不使用NSNotificationCenter
?
NSNotificationCenter 对象(或简称为通知中心)提供了一种在程序中广播信息的机制。 NSNotificationCenter 对象本质上是一个通知调度表。
您可以在单例或普通的一个中添加通知观察者,当您需要发送消息时,只需发布正确的通知即可。然后观察者将管理该操作。
More detail on NSNotificationCenter.
【讨论】:
这不是视图控制器想要发送的广播“通知”,它是一个带有返回值的方法调用,所以我不会认为观察者是合适的。 @RobertAtkins "Broadcasting" 只是一个选择,你可以将观察者添加到一个特殊的,一对一的可以,而且你可以返回它的价值。非常方便。 您能否添加一个示例,一个需要模型的 VC 如何获得对它的引用?如果您的解决方案意味着为每个模型对象观察一个通知,那么它看起来相当复杂。 @zoul 你的意思是[[NSNotificationCenter defaultCenter] postNotificationName:<#(nonnull NSString *)#> object:<#(nullable id)#> userInfo:<#(nullable NSDictionary *)#>
?两个选项:1. 你可以将模型实例作为对象发送,2. 如果你有一个复杂的结构来通知观察者,你可以将它作为字典存储到 userInfo 中。
感谢您的回复!问题是,使用传统的 setter 或构造函数注入,VC 中的开销为零,需要一些依赖。它只是为依赖项公开一个构造函数参数或一个设置器,并从外部接收它。使用通知中心解决方案,是否必须为每个依赖项订阅通知?这感觉有点矫枉过正。【参考方案3】:
这不是获取对视图控制器对象的引用吗?如果您使用情节提要,那么窗口 rootViewController
或使用的 segues 将为您提供正确的视图控制器对象。
即: 应用启动时视图控制器的实例是
self.window.rootViewController
当您在场景(视图控制器)之间切换时:
[segue destinationViewController]
或[segue sourceViewController]
如果您使用的是 xibs,您甚至可以使用界面构建器中的外部对象(代理对象)来提供模型对象。唯一的问题是您必须自己掌握 nib 实例化。
【讨论】:
【参考方案4】:理想情况下,即使在使用情节提要时,第三方开发人员也可以使用自己的构造函数/初始化程序。
在此之前,您可以使用 setter/property 注入和中介模式,尤其是因为您已经习惯了最佳实践和松散耦合。
我在这里写过:http://cocoapatterns.com/passing-data-between-view-controllers/
【讨论】:
在链接页面上的示例中,我不确定 ViewControllerA 和 ViewControllerB 如何获取对您的中介对象的引用?中介是否有一个 sharedInstance 类方法来返回它的单例?如果是这样的话,我觉得这是一种渐进式的改进(相对于 VC 自己管理对每个资源的引用),但我认为它并不能彻底解决问题——实际上它引入了一些不必要的耦合,当中介者授予对属性的访问权时VC 需要,但其他人不需要。 @RoberAtkins 所有 VC 都持有对中介者的引用,中介者本身作为 segue 的一部分注入到中介者中。初始 VC 从 Composition Root 获取中介(在 iOS 的情况下为 AppDelegate 或您创建对象图的任何地方)。这可以通过协议(例如名为 Transitionable)以更简洁的方式强加,所有 VC 都符合.如果您愿意,调解员也可以是单身人士。我个人不知道,尽管我明白为什么有人会在这个用例中争论它。 另外,正如你所说,它可能会引入一些不必要的耦合,但它也有助于保持整体耦合较低,尤其是与“每个人为自己”的方法相比时,这种方法很容易导致耦合爆炸.当然,模式的好处在更大的项目上大放异彩,模式/开销的引入对于 2-4 场景应用程序来说显然是一种过度杀伤力。不过,主要优势绝对是它有助于维护和替换 VC,方法是将 segues 期间需要进行的更改限制在一个地方,即中介。以上是关于如何将依赖项注入 iOS 视图控制器?的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 Unity 将依赖项注入 ASP.NET Web API 控制器
Simple Injector 无法在 Web API 控制器中注入依赖项