ios项目依赖注入

依赖注入(Dependency Injection)

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

什么是依赖注入(Dependency Injection)?

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

依赖注入是最早spring和Piconcontainer等提出,如今已经是一个缺省主流模式,并扩展到前端如Angular.js等等。

1. 依赖

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

#import"ViewControllerB.h"@implementationViewControllerA- (void)buttonTapped{    ViewControllerB *vc = [[ViewControllerB alloc] init];    [self.navigationControllerpushViewController:vc animated:YES];}

仔细看这段代码我们会发现存在一些问题:

(1). 如果现在要改变ViewControllerB生成方式,如需要用initWithOrderid:(NSString * orderid)初始化vc,需要修改ViewControllerA代码;

(2). 如果想测试不同ViewControllerB对象对ViewControllerA的影响很困难,因为ViewControllerB 的初始化被写死在了ViewControllerA` 的构造函数中;

(3). 如果[[ViewControllerB alloc] init]过程非常缓慢,单测时我们希望用已经初始化好的ViewControllerB对象Mock掉这个过程也很困难。

2. 依赖注入

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

@interfaceViewControllerA()@property(nonatomic,readonly) ViewControllerB *vcB;@end@implementationViewControllerA// vcB是在ViewControllerA被创建之前被创建的并且作为参数传进来,// 调用者如果想,还可以自定义。- (instancetype)initWithEngine:(ViewControllerB *)vcB  {    ...    _vcB = vcB;returnself;  }@end

上面代码中,我们将vcB对象作为构造函数的一个参数传入。在调用ViewControllerA的构造方法之前外部就已经初始化好了vcB对象。像这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入。

现在我们发现上面1中存在的两个问题都很好解决了,简单的说依赖注入主要有两个好处:

解耦,将依赖之间解耦。

因为已经解耦,所以方便做单元测试,尤其是Mock测试。

那么问题来了,如何学习Dependency Injection呢 ?iOS有关DI依赖注入的框架比较好用的有两个:Objection 和 Typhoon.下面就从几个方便来介绍下这两个框架

一:Objection 和 Typhoon这两个框架有什么区别呢 其实这两个框架各有优势:

Objection框架,使用起来比较灵活,用法比较简单。示例代码如下:

属性注册:

@classEngine,Brakes;@interfaceCar:NSObject{    Engine *engine;    Brakes *brakes;BOOLawake;  }// Will be filled in by objection@property(nonatomic,strong) Engine *engine;// Will be filled in by objection@property(nonatomic,strong) Brakes *brakes;@property(nonatomic)BOOLawake;@implementationCarobjection_requires(@"engine", @"brakes")//属性的依赖注入@synthesizeengine, brakes, awake;@end


方法注入:

@implementation Truckobjection_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)defaultQuest;@end

属性注入:

- (Knight*)cavalryMan{return[TyphoonDefinitionwithClass:[CavalryManclass]configuration:^(TyphoonDefinition*definition) {    [definitioninjectProperty:@selector(quest)with:[selfdefaultQuest]];    [definitioninjectProperty:@selector(damselsRescued)with:@(12)];}];}

方法注入:

- (Knight*)knightWithMethodInjection{return[TyphoonDefinitionwithClass:[Knightclass]configuration:^(TyphoonDefinition*definition) {    [definitioninjectMethod:@selector(setQuest:andDamselsRescued:)parameters:^(TyphoonMethod*method) {        [methodinjectParameterWith:[selfdefaultQuest]];        [methodinjectParameterWith:@321];    }];}];}

3.当然还有一些硬性的区别就是Typhoon现在已经支持Swift

4.两者维护时间都超过2年以上。

Tythoon官方介绍的优势:

1)Non-invasive. No macrosorXML required. Uses powerful ObjC runtime instrumentation.2)No magic strings – supports IDE refactoring, code-completionandcompile-timechecking.3)Provides full-modularizationandencapsulationofconfiguration details. Let your architecturetella story.4)Dependencies declaredinany order. (The orderthatmakes sensetohumans).5)Makesiteasytohave multiple configurationsofthesame base-classorprotocol.6)Supports injectionofview controllersandstoryboard integration. Supports both initializerandpropertyinjection, plus life-cycle management.7)Powerful memory management features. Provides pre-configured objects,withoutthememory overheadofsingletons.8)Excellent supportforcircular dependencies.9)Lean. Has a very low footprint, soisappropriateforCPUandmemory constrained devices.10)While being feature-packed, Typhoon weighs-inatjust3000linesofcodeintotal.11)Battle-tested — usedinall kindsofAppstore-featured apps.

大体翻译过来:

1)非侵入性。不需要宏或XML。使用强大的ObjC运行时仪器。2)没有魔法字符串——支持IDE重构,完成和编译时检查。3)提供full-modularization和封装的配置细节。让你的架构告诉一个故事。4)依赖关系中声明的任何顺序。(对人类有意义的顺序)。5)很容易有多个配置相同的基类或协议。6)支持注射的视图控制器和故事板集成。同时支持初始化器和属性注入,以及生命周期管理。7)强大的内存管理功能。提供预配置对象,没有单件的内存开销。8)优秀的支持循环依赖。9)精益。占用很低,所以适合CPU和内存受限的设备。10),功能强大,台风重总共只有3000行代码。11)久经沙场,用于各种Appstore-featured应用。

针对这两个框架网上教程并不多,收集了一些比较有用的资料。最主要的用法还得看官方文档分别在:ObjectionTyphoon

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{    [superviewDidLoad];UIButton*button = [UIButtonbuttonWithType:UIButtonTypeSystem];    button.frame= CGRectMake(100,100,100,30);    [button setTitle:@"Button"forState:UIControlStateNormal];    [button addTarget:selfaction:@selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside];    [self.viewaddSubview:button];}- (void)buttonTapped{    ViewControllerB *vc = [[ViewControllerB alloc] init];    [self.navigationControllerpushViewController:vc animated:YES];}

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

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

JSObjectionInjector *injector = [JSObjection defaultInjector];// [1]UIViewController *vc = [injector getObject:@protocol(ViewControllerAProtocol)]; // [2]vc.backgroundColor= [UIColorlightGrayColor];// [3]UINavigationController*nc = [[UINavigationControlleralloc] initWithRootViewController:vc];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即可。

@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]

[1] 获取默认的injector

[2] 如果默认的injector不存在,就新建一个

[3] 往这个injector里注册我们的Module

[4] 设置该injector为默认的injector

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

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

@protocolViewControllerAProtocol@property(nonatomic) NSUInteger currentIndex;@property(nonatomic)UIColor*backgroundColor;@end@interfaceViewControllerA:UIViewController@end

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

总体来说,这个lib还是挺靠谱的,已经维护了两年多,也有一些项目在用,对于提高开发成员的效率也会有不少的帮助,可以考虑尝试下

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容