我看到了在视图控制器(注入)之间传递依赖关系的好处,而不是使用全局状态。然而,我很好奇人们如何在实践中实现这一点。
在最简单的情况下,很容易在两个视图控制器之间传递单个模型:
MyViewController *vc = [[MyViewController alloc] init];
vc.model = model;
[self.navigationController pushViewController:vc animated:YES];
我发现扩展这种方法存在两个问题:
1)您可能拥有所有视图控制器(位置管理器,对象存储库等)所需的一些服务。因此,您最终需要记住为每个视图控制器设置的依赖项列表:
MyViewController *vc = [[MyViewController alloc] init];
vc.model = model;
vc.locationManager = locationManager;
vc.objectStore = objectStore;
...
[self.navigationController pushViewController:vc animated:YES];
2)第二个问题与第一个问题有关:在推送视图控制器之前,您实际上并未强制执行这些依赖项。我想你可以编写一个需要所有依赖项的init方法,但你仍然无法强制执行它。它也会啰嗦,如果你想稍后添加一个依赖,那将是一个巨大的痛苦。
处理这些问题的方法有哪些?似乎很多Obj-C人都没有使用依赖注入框架。我能想到的一种方法是创建一个包含所有共享依赖项的AppContext类,然后将其传递给所有视图控制器。
此外,通常您使用接口而不是实现声明依赖项(至少在Java中),因此您可以模拟它们以进行单元测试。我没有看到很多Obj-C人员以这种方式使用协议。那么你如何模拟单元测试的依赖?
答案 0 :(得分:2)
依赖注入的KEY是应该在构造函数中指定所有硬依赖项!
因此,您的构造函数应如下所示:
- (id)initWithModel:(Model *)model locationManager:(LocationManager *)locationManager objectStore:(ObjectStore *)objectStore;
任何不是可选的并且提供类本身不应该负责构造的工具应该在构造函数中指定。这也是测试中的一个巨大的辅助,因为对象本身可以提供它的依赖关系的模拟版本,用于隔离网络套接字,存储后端等......
如果这看起来很麻烦,并且您觉得构造函数看起来太大或太丑,这不是因为这些应该是属性或隐式依赖!这可能意味着该类试图做太多,或者它的依赖关系可以被考虑到复合对象中。
我在一个非常大的(100,000行)生产的iPhone应用程序上工作,并且对测试驱动的开发非常坚定。实际上,我实现依赖注入的方法是通过协议。您完全正确地认为,您在互联网上看到的示例代码很少以这种方式编写,但它是100%正确且恰当的。
我一直使用的一个非常有用的模式是有两个构造函数,一个暴露了所有依赖项,一个构造函数参数较少,并且隐式默认值(用于生产)。
例如:
// Fully exposed constructor, for easy unit testing.
- (id)initWithHost:(NSString *)host storageProvider:(id<StorageProvider>)storageProvider socketFactory:(id<SocketFactory>)socketFactory;
// Constructor that calls the former, with fully-functional defaults passed into former constructor implicitly.
- (id)initWithHost;
这种模式与良好的模拟框架(OCMockito或OCMock都很好)相结合,将使您在设计干净,诚实和高度可测试的代码方面走得非常远。 :)
答案 1 :(得分:1)
Objection可能是您正在寻找的答案。它是一个依赖注入框架,它提供了manual依赖注入的替代方案。
例如,
@implementation MyViewController
objection_register(MyViewController)
objection_initializer(initWithNibName:bundle:, @"MyNibName")
objection_requires(@"model", @"locationManager", @"objectStore", @"objectFactory")
@synthesize model;
@synthesize locationManager;
@synthesize objectStore;
@synthesize objectFactory;
@end
我们使用注射器初始化它(有关详细信息,请参阅guide):
MyViewController *controller = [self.objectFactory getObject:[MyViewController class]];
[self.navigationController pushViewController:controller animated:YES];
异议倾向于使用属性注入而不是'构造函数'注入。主要是因为Objective-C没有构造函数作为语言的一部分(它使用alloc init
作为约定)。 Objective-C运行时提供有关消息参数的少量类型信息。但是,运行时提供了大量有关属性的信息,Key-Value Coding API功能强大且通常比NSInvocation更安全。