Injineer 比 Objective-C 的其他 DI 框架更简单,但同样强大。此框架在您的代码中引入了无依赖,因此使用它几乎是完全透明的(见下文)。
我将另一个 DI 框架(Typhoon)的示例转换为使用 Injineer。您可以在示例文件夹中找到它。但是我没有更新单元测试,因为它们依赖于 Typhoon 的自定义功能。
整个项目只由一个类组成:INJContainer。这是您注册对象及其依赖关系的地方。
您可以添加两种类型的对象:实例和提供者。
- (void) addInstance: (id) instance
forName: (NSString *) name;
实例只是具有名称的对象。它们没有依赖关系。如果对象依赖于已注册到实例的名称,则将注入您在此处设置的值。
- (void) addProviderForName: (NSString *) name
depedencies: (NSArray *) dependencies
creator: (id (^)(NSDictionary *values)) creator;
- (void) addProviderForName: (NSString *) name
depedencies: (NSArray *) dependencies
options: (INJProviderOptions) options
creator: (id (^)(NSDictionary *values)) creator;
提供者比实例更复杂。它们也有名称,但也可以有依赖关系,这些依赖关系只是其他提供者或实例的名称。每个提供者都有一个创建器块,其任务是构建并返回一个对象。它接受一个字典作为参数,该字典包含每个依赖名称的解决值。
如果您使用构造函数注入,可以将值字典中相应对象传递给您的方法。
对于创建对象的每个属性,如果它为 nil,有设置器并且有一个来自依赖列表的名称,则框架将尝试将其设置为相应依赖关系的解决值。您可以通过传递 INJProviderOptionManualInit 选项来关闭此行为。
默认情况下,每当另一个提供者依赖于第一个提供者的名称时,都会调用提供者的构造函数块。您可以通过添加 INJProviderOptionSingleton 选项来使您的提供者成为单例。在这种情况下,提供者的构造函数块仅被调用一次,返回的实例将赋予所有其他依赖于它的提供者。
在指定所有实例和提供者之后,您可以调用 -checkForErrors 方法。这个方法确保您没有依赖于未添加到容器中的名称。这种情况通常是由于拼写错误导致的。它还会对依赖关系图进行拓扑排序,以找出任何循环依赖,并抛出异常列出它们。
有时,当您所依赖的对象被创建时,您希望拥有更多的控制权。视图控制器就是这种情况的一个很好的例子。比如说,我们有以下情况
[container addInstance: [[ProductsService alloc] init]
forName: "productsService"];
[container addProviderForName: @"productsListViewController"
dependencies: @[ @"productsService", @"productDetailsViewController" ]
options: 0
creator: (id (^)(NSDictionary *values)) creator {
return [[ProductsListViewController alloc] init];
}];
[container addProviderForName: @productDetailsViewController"
dependencies: @[ @"productsService" ]
options: 0
creator: (id (^)(NSDictionary *values)) creator {
return [[ProductDetailsViewController alloc] init];
}];
两个视图控制器都依赖于 productsService,并且 ProductsListViewController 也依赖于 ProductDetailsViewController,因为后者在从列表中选择产品时打开。这种安排的问题在于详细信息控制器只创建一次。相反,您想要每次选择产品时都创建一个新的控制器。为了实现这一点,您需要将依赖项的名称从 'productDetailsViewController' 更改为 'productDetailViewControllerProvider'。这将这样做:而不是为详细信息视图控制器创建一个新实例并将其注入列表视图控制器中,它会注入一个返回 ProductDetailsViewController 实例的块。也就是说
而不是 ProductsListViewController 有一个属性
@property (nonatomic, string) ProductDetailsViewController *productDetailsViewController
它将有
@property (nonatomic, string) ProductDetailsViewController *(^productDetailsViewControllerProvider)(void)
当您想要一个新的详细信息视图控制器实例时,只需使用 productDetailsViewControllerProvider() 返回的对象即可。这是框架唯一与您的代码组织冲突的地方,因为您需要注入一个返回实例的块,在这种情况下。但是,您只是遵循了一个约定,而不是在您的视图控制器中导入其他代码或库。
这通常是一个代码问题,Injineer 默认不支持它们,但如果您想,仍然可以进行。假设您有以下这些依赖
[container addProviderForName: @"rootViewController"
dependencies: @[ @"productsListViewController" ]
options: INJProviderOptionSingleton
creator: (id (^)(NSDictionary *values)) creator {
return [[RootViewController alloc] init];
}];
[container addProviderForName: @"productsListViewController"
dependencies: @[ @"productsService", @"productDetailsViewControllerProvider" ]
options: 0
creator: (id (^)(NSDictionary *values)) creator {
return [[ProductsListViewController alloc] init];
}];
[container addProviderForName: @"productsService"
dependencies: @[ @"rootViewController" ]
options: INJProviderOptionSingleton
creator: (id (^)(NSDictionary *values)) creator {
return [[ProductsService alloc] init];
}];
这个依赖关系图包含了一个循环依赖,形式为 rootViewController -> productsListViewController -> productsService -> rootViewController。为了解决这个问题,您可以将 productService 修改为依赖于 rootViewControllerProvider,从而打破循环。当您需要 rootViewController 的实例时,只需使用 rootViewControllerProvider() 返回的那个即可。