简介

依赖注入(Dependency Injection)最大的特点就是:帮助我们开发出松散耦合(loose coupled)、可维护、可测试的代码和程序。这条原则的做法是大家熟知的面向接口,或者说是面向抽象编程。 众所周知该编程思想在各大语言中都有体现如javeC++PHP 以及 .net中。当然设计模式的广泛程度远远大于这些,iOS 当然也不例外。 本文主要介绍本人在学习Dependency Injection的时候的学习过程以及对一些学习资料的总结,主要介绍iOS中的两大框架ObjectionTyphoon 。 在 Android上比较流行的有 RoboGuiceDagger 等.

定义 

依赖注入(Dependency Injection) 是一个将行为从依赖中分离的技术,简单地说,它允许开发者定义一个方法函数依赖于外部其他各种交互,而不需要编码如何获得这些外部交互的实例。 这样就在各种组件之间解耦,从而获得干净的代码,相比依赖的硬编码, 一个组件只有在运行时才调用其所需要的其他组件,因此在代码运行时,通过特定的框架或容器,将其所需要的其他依赖组件进行注入,主动推入。
依赖注入是最早SpringPiconcontainer等提出,如今已经是一个缺省主流模式,并扩展到前端如Angular.js等等。

1. 依赖

如果在 Class A中,有 Class B的实例,则称 Class AClass B 有一个依赖。例如下面类 ViewControllerA 中用到一个 ViewControllerB 对象,我们就说类 ViewControllerA 对类 ViewControllerB 有一个依赖。

  1. #import "ViewControllerB.h"
  2. @implementation ViewControllerA
  3. - (void)buttonTapped{
  4. ViewControllerB *vc = [[ViewControllerB alloc] init];
  5. [self.navigationController pushViewController:vc animated:YES];
  6. }

仔细看这段代码我们会发现存在一些问题:
(1). 如果现在要改变 ViewControllerB 生成方式,如需要用initWithOrderid:(NSString * orderid)初始化 vc,需要修改 ViewControllerA 代码;
(2). 如果想测试不同 ViewControllerB 对象对 ViewControllerA 的影响很困难,因为 ViewControllerB 的初始化被写死在了ViewControllerA的构造函数中;<br />(3). 如果[[ViewControllerB alloc] init]过程非常缓慢,单测时我们希望用已经初始化好的ViewControllerB对象Mock` 掉这个过程也很困难。

2. 依赖注入

上面将依赖在构造函数中直接初始化是一种 Hard init 方式,弊端在于两个类不够独立,不方便测试。我们还有另外一种 Init 方式,如下:

  1. @interface ViewControllerA ()
  2. @property (nonatomic, readonly) ViewControllerB *vcB;
  3. @end
  4. @implementation ViewControllerA
  5. // vcB是在ViewControllerA被创建之前被创建的并且作为参数传进来,
  6. // 调用者如果想,还可以自定义。
  7. - (instancetype)initWithEngine:(ViewControllerB *)vcB
  8. {
  9. ...
  10. _vcB = vcB;
  11. return self;
  12. }
  13. @end

上面代码中,我们将 vcB 对象作为构造函数的一个参数传入。在调用 ViewControllerA 的构造方法之前外部就已经初始化好了 vcB 对象。像这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入
现在我们发现上面 1中存在的两个问题都很好解决了,简单的说依赖注入主要有两个好处:

  • 解耦,将依赖之间解耦。
  • 因为已经解耦,所以方便做单元测试,尤其是 Mock测试。

    常用框架

  • Objection

  • Typhoon

    最主要的用法还得看官方文档分别在:ObjectionTyphoon objc.io官网的博文 Dependency Injection 和 Typhoon原创大神(Graham Lee)的文章 > Dependency Injection, iOS and You

    代码分析

    Objection 是一个轻量级的依赖注入框架,受Guice的启发,Google Wallet 也是使用的该项目。「依赖注入」是面向对象编程的一种设计模式,用来减少代码之间的耦合度。通常基于接口来实现,也就是说不需要new一个对象,而是通过相关的控制器来获取对象。2013年最火的PHP框架 laravel 就是其中的典型。

    场景

    ViewControllerA push一个ViewControllerBViewControllerA需要import ViewControllerB,也就是对ViewControllerB产生了依赖。依赖的东西越多,维护起来就越麻烦,也容易出现循环依赖的问题,而objection正好可以处理这些问题。

    实现方法

    先定义一个协议(protocol),然后通过objection来注册这个协议对应的class,需要的时候,可以获取该协议对应的object。对于使用方无需关心到底使用的是哪个Class,反正该有的方法、属性都有了(在协议中指定)。这样就去除了对某个特定Class的依赖。也就是通常所说的「面向接口编程」

    1. JSObjectionInjector *injector = [JSObjection defaultInjector]; // [1]
    2. UIViewController <ViewControllerAProtocol> *vc = [injector getObject:@protocol(ViewControllerAProtocol)]; // [2]
    3. vc.backgroundColor = [UIColor lightGrayColor]; // [3]
    4. UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
    5. self.window.rootViewController = nc;
  • [1] 获取默认的injector,这个injector已经注册过ViewControllerAProtocol了。

  • [2] 获取ViewControllerAProtocol对应的Object
  • [3] 拿到VC后,设置它的某些属性,比如这里的backgroundColor,因为在ViewControllerAProtocol里有定义这个属性,所以不会有warning

可以看到这里没有引用ViewControllerA。再来看看这个ViewControllerAProtocol是如何注册到injector中的,这里涉及到了Module,对Protocol的注册都是在Module中完成的。Module只要继承JSObjectionModule这个Class即可。

  1. @interface ViewControllerAModule : JSObjectionModule
  2. @end
  3. @implementation ViewControllerAModule
  4. + (void)load{
  5. JSObjectionInjector *injector = [JSObjection defaultInjector];
  6. injector = injector ? : [JSObjection createInjector];
  7. injector = [injector withModule:[[ViewControllerAModule alloc] init]];
  8. [JSObjection setDefaultInjector:injector];
  9. }
  10. - (void)configure
  11. {
  12. [self bindClass:[ViewControllerA class] toProtocol:@protocol(ViewControllerAProtocol)];
  13. }
  14. @end

绑定操作是在configure方法里进行的,这个方法在被添加到injector里时会被自动触发。

这段代码可以直接放到 + (void)load里执行,这样就可以避免在AppDelegateimport各种Module

  1. JSObjectionInjector *injector = [JSObjection defaultInjector]; // [1]
  2. injector = injector ? : [JSObjection createInjector]; // [2]
  3. injector = [injector withModule:[[ViewControllerAModule alloc] init]]; // [3]
  4. [JSObjection setDefaultInjector:injector]; // [4]
  • [1] 获取默认的injector
  • [2] 如果默认的 injector 不存在,就新建一个
  • [3] 往这个 injector 里注册我们的 Module
  • [4] 设置该 injector 为默认的 injector

因为我们无法直接获得对应的Class,所以必须要在协议里定义好对外暴露的方法和属性,然后该Class也要实现该协议。

  1. @protocol ViewControllerAProtocol <NSObject>
  2. @property (nonatomic) NSUInteger currentIndex;
  3. @property (nonatomic) UIColor *backgroundColor;
  4. @end
  5. @interface ViewControllerA : UIViewController <ViewControllerAProtocol>
  6. @end

通过Objection实现依赖注入后,就能更好地实现SRP(Single Responsibility Principle),代码更简洁,心情更舒畅,生活更美好。