依赖注入
最大的特色就是:帮助咱们开发出松散耦合(loose coupled)
、可维护、可测试的代码和程序。这条原则的作法是你们熟知的面向接口,或者说是面向抽象编程。 众所周知该编程思想在各大语言中都有体现如jave
、 C++
、 PHP
以及 .net
中。固然设计模式的普遍程度远远大于这些,iOS
固然也不例外。 本文主要介绍本人在学习Dependency Injection
的时候的学习过程以及对一些学习资料的总结,主要介绍iOS
中的两大框架Objection 和 Typhoon 。 在 Android
上比较流行的有 RoboGuice 和 Dagger 等.前端
依赖注入(Dependency Injection)
是一个将行为从依赖中分离的技术,简单地说,它容许开发者定义一个方法函数依赖于外部其余各类交互,而不须要编码如何得到这些外部交互的实例。 这样就在各类组件之间解耦,从而得到干净的代码,相比依赖的硬编码, 一个组件只有在运行时才调用其所须要的其余组件,所以在代码运行时,经过特定的框架或容器,将其所须要的其余依赖组件进行注入,主动推入。ios
依赖注入是最先Spring
和Piconcontainer
等提出,现在已是一个缺省主流模式,并扩展到前端如Angular.js
等等。laravel
若是在 Class A
中,有 Class B
的实例,则称 Class A
对 Class B
有一个依赖。例以下面类 ViewControllerA
中用到一个 ViewControllerB
对象,咱们就说类 ViewControllerA
对类 ViewControllerB
有一个依赖。git
#import "ViewControllerB.h" @implementation ViewControllerA - (void)buttonTapped{ ViewControllerB *vc = [[ViewControllerB alloc] init]; [self.navigationController pushViewController:vc animated:YES]; }
仔细看这段代码咱们会发现存在一些问题:github
(1). 若是如今要改变 ViewControllerB
生成方式,如须要用initWithOrderid:(NSString * orderid)
初始化 vc
,须要修改 ViewControllerA
代码;编程
(2). 若是想测试不一样 ViewControllerB
对象对 ViewControllerA
的影响很困难,由于 ViewControllerB 的初始化被写死在了
ViewControllerA` 的构造函数中;设计模式
(3). 若是[[ViewControllerB alloc] init]
过程很是缓慢,单测时咱们但愿用已经初始化好的 ViewControllerB
对象 Mock
掉这个过程也很困难。架构
上面将依赖在构造函数中直接初始化是一种 Hard init
方式,弊端在于两个类不够独立,不方便测试。咱们还有另一种 Init
方式,以下:app
@interface ViewControllerA () @property (nonatomic, readonly) ViewControllerB *vcB; @end @implementation ViewControllerA // vcB是在ViewControllerA被建立以前被建立的而且做为参数传进来, // 调用者若是想,还能够自定义。 - (instancetype)initWithEngine:(ViewControllerB *)vcB { ... _vcB = vcB; return self; } @end
上面代码中,咱们将 vcB
对象做为构造函数的一个参数传入。在调用 ViewControllerA
的构造方法以前外部就已经初始化好了 vcB
对象。像这种非本身主动初始化依赖,而经过外部来传入依赖的方式,咱们就称为依赖注入
。框架
如今咱们发现上面 1
中存在的两个问题都很好解决了,简单的说依赖注入主要有两个好处:
解耦,将依赖之间解耦。
由于已经解耦,因此方便作单元测试,尤为是 Mock
测试。
Objection
框架,使用起来比较灵活,用法比较简单。示例代码以下:属性注册:
@class Engine, Brakes; @interface Car : NSObject { Engine *engine; Brakes *brakes; BOOL awake; } // Will be filled in by objection @property(nonatomic, strong) Engine *engine; // Will be filled in by objection @property(nonatomic, strong) Brakes *brakes; @property(nonatomic) BOOL awake; @implementation Car objection_requires(@"engine", @"brakes") //属性的依赖注入 @synthesize engine, brakes, awake; @end
方法注入:
@implementation Truck objection_requires(@"engine", @"brakes") objection_initializer(truckWithMake:model:)//方法的依赖注入 + (instancetype)truckWithMake:(NSString *) make model: (NSString *)model { ... } @end
2.对比来讲Typhoon
的使用起来就比较规范,首先须要建立一个TyphoonAssembly
的子类。其须要注入的方法和属性都须要写在这个统一个子类中,固然能够实现不一样的子类来完成不一样的功能:
@interface MiddleAgesAssembly : TyphoonAssembly - (Knight*)basicKnight; - (Knight*)cavalryMan; - (id<Quest>)defaultQuest; @end
属性注入:
- (Knight *)cavalryMan { return [TyphoonDefinition withClass:[CavalryMan class] configuration:^(TyphoonDefinition *definition) { [definition injectProperty:@selector(quest) with:[self defaultQuest]]; [definition injectProperty:@selector(damselsRescued) with:@(12)]; }]; }
方法注入:
- (Knight *)knightWithMethodInjection { return [TyphoonDefinition withClass:[Knight class] configuration:^(TyphoonDefinition *definition) { [definition injectMethod:@selector(setQuest:andDamselsRescued:) parameters:^(TyphoonMethod *method) { [method injectParameterWith:[self defaultQuest]]; [method injectParameterWith:@321]; }]; }]; }
3.固然还有一些硬性的区别就是Typhoon
如今已经支持Swift
。
4.二者维护时间都超过2
年以上。
Tythoon
官方介绍的优点:
1)Non-invasive. No macros or XML required. Uses powerful ObjC runtime instrumentation. 2)No magic strings – supports IDE refactoring, code-completion and compile-time checking. 3)Provides full-modularization and encapsulation of configuration details. Let your architecture tell a story. 4)Dependencies declared in any order. (The order that makes sense to humans). 5)Makes it easy to have multiple configurations of the same base-class or protocol. 6)Supports injection of view controllers and storyboard integration. Supports both initializer and property injection, plus life-cycle management. 7)Powerful memory management features. Provides pre-configured objects, without the memory overhead of singletons. 8)Excellent support for circular dependencies. 9)Lean. Has a very low footprint, so is appropriate for CPU and memory constrained devices. 10)While being feature-packed, Typhoon weighs-in at just 3000 lines of code in total. 11)Battle-tested — used in all kinds of Appstore-featured apps.
大致翻译过来:
1)非侵入性。不须要宏或XML。使用强大的ObjC运行时仪器。 2)没有魔法字符串——支持IDE重构,完成和编译时检查。 3)提供full-modularization和封装的配置细节。让你的架构告诉一个故事。 4)依赖关系中声明的任何顺序。(对人类有意义的顺序)。 5)很容易有多个配置相同的基类或协议。 6)支持注射的视图控制器和故事板集成。同时支持初始化器和属性注入,以及生命周期管理。 7)强大的内存管理功能。提供预配置对象,没有单件的内存开销。 8)优秀的支持循环依赖。 9)精益。占用很低,因此适合CPU和内存受限的设备。 10),功能强大,台风重总共只有3000行代码。 11)久经沙场,用于各类Appstore-featured应用。
针对这两个框架网上教程并很少,收集了一些比较有用的资料。最主要的用法还得看官方文档分别在:Objection 和 Typhoon
objc.io
官网的博文 Dependency Injection 和 Typhoon原创大神(Graham Lee)的文章 Dependency Injection, iOS and You
不看后悔一生^_^
Objection
是一个轻量级的依赖注入框架,受Guice的启发,Google Wallet 也是使用的该项目。「依赖注入」是面向对象编程的一种设计模式,用来减小代码之间的耦合度。一般基于接口来实现,也就是说不须要new一个对象,而是经过相关的控制器来获取对象。2013年最火的PHP框架 laravel
就是其中的典型。
假设有如下场景:ViewControllerA.view里有一个button,点击以后push一个ViewControllerB,最简单的写法相似这样:
- (void)viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; button.frame = CGRectMake(100, 100, 100, 30); [button setTitle:@"Button" forState:UIControlStateNormal]; [button addTarget:self action:@selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; } - (void)buttonTapped { ViewControllerB *vc = [[ViewControllerB alloc] init]; [self.navigationController pushViewController:vc animated:YES]; }
这样写的一个问题是,ViewControllerA
须要import ViewControllerB
,也就是对ViewControllerB
产生了依赖。依赖的东西越多,维护起来就越麻烦,也容易出现循环依赖的问题,而objection
正好能够处理这些问题。
实现方法是:先定义一个协议(protocol),而后经过objection来注册这个协议对应的class,须要的时候,能够获取该协议对应的object。对于使用方无需关心到底使用的是哪一个Class,反正该有的方法、属性都有了(在协议中指定)。这样就去除了对某个特定Class的依赖。也就是一般所说的「面向接口编程」。
JSObjectionInjector *injector = [JSObjection defaultInjector]; // [1] UIViewController <ViewControllerAProtocol> *vc = [injector getObject:@protocol(ViewControllerAProtocol)]; // [2] vc.backgroundColor = [UIColor lightGrayColor]; // [3] UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc]; self.window.rootViewController = nc;
injector
,这个injector
已经注册过ViewControllerAProtocol
了。ViewControllerAProtocol
对应的Object
。VC
后,设置它的某些属性,好比这里的backgroundColor
,由于在ViewControllerAProtocol
里有定义这个属性,因此不会有warning
。能够看到这里没有引用ViewControllerA
。再来看看这个ViewControllerAProtocol
是如何注册到injector
中的,这里涉及到了Module
,对Protocol
的注册都是在Module
中完成的。Module
只要继承JSObjectionModule
这个Class
便可。
@interface ViewControllerAModule : JSObjectionModule @end @implementation ViewControllerAModule + (void)load{ JSObjectionInjector *injector = [JSObjection defaultInjector]; injector = injector ? : [JSObjection createInjector]; injector = [injector withModule:[[ViewControllerAModule alloc] init]]; [JSObjection setDefaultInjector:injector]; } - (void)configure { [self bindClass:[ViewControllerA class] toProtocol:@protocol(ViewControllerAProtocol)]; } @end
绑定操做是在configure
方法里进行的,这个方法在被添加到injector
里时会被自动触发。
JSObjectionInjector *injector = [JSObjection defaultInjector]; // [1] injector = injector ? : [JSObjection createInjector]; // [2] injector = [injector withModule:[[ViewControllerAModule alloc] init]]; // [3] [JSObjection setDefaultInjector:injector]; // [4]
injector
injector
不存在,就新建一个injector
里注册咱们的 Module
injector
为默认的 injector
这段代码能够直接放到 + (void)load
里执行,这样就能够避免在AppDelegate
里import
各类Module
。
由于咱们没法直接得到对应的Class
,因此必需要在协议里定义好对外暴露的方法和属性,而后该Class
也要实现该协议。
@protocol ViewControllerAProtocol <NSObject> @property (nonatomic) NSUInteger currentIndex; @property (nonatomic) UIColor *backgroundColor; @end @interface ViewControllerA : UIViewController <ViewControllerAProtocol> @end
经过Objection
实现依赖注入后,就能更好地实现SRP(Single Responsibility Principle)
,代码更简洁,心情更舒畅,生活更美好。
整体来讲,这个lib仍是挺靠谱的,已经维护了两年多,也有一些项目在用,对于提升开发成员的效率也会有很多的帮助,能够考虑尝试下。