ios項(xiàng)目依賴(lài)注入

依賴(lài)注入(Dependency Injection)

依賴(lài)注入最大的特點(diǎn)就是:幫助我們開(kāi)發(fā)出松散耦合(loose coupled)、可維護(hù)、可測(cè)試的代碼和程序。這條原則的做法是大家熟知的面向接口,或者說(shuō)是面向抽象編程。 眾所周知該編程思想在各大語(yǔ)言中都有體現(xiàn)如jave、C++、PHP以及.net中。當(dāng)然設(shè)計(jì)模式的廣泛程度遠(yuǎn)遠(yuǎn)大于這些,iOS當(dāng)然也不例外。 本文主要介紹本人在學(xué)習(xí)Dependency Injection的時(shí)候的學(xué)習(xí)過(guò)程以及對(duì)一些學(xué)習(xí)資料的總結(jié),主要介紹ios中的兩大框架ObjectionTyphoon。 在Android上比較流行的有RoboGuiceDagger等.

什么是依賴(lài)注入(Dependency Injection)?

依賴(lài)注入(Dependency Injection)是一個(gè)將行為從依賴(lài)中分離的技術(shù),簡(jiǎn)單地說(shuō),它允許開(kāi)發(fā)者定義一個(gè)方法函數(shù)依賴(lài)于外部其他各種交互,而不需要編碼如何獲得這些外部交互的實(shí)例。 這樣就在各種組件之間解耦,從而獲得干凈的代碼,相比依賴(lài)的硬編碼, 一個(gè)組件只有在運(yùn)行時(shí)才調(diào)用其所需要的其他組件,因此在代碼運(yùn)行時(shí),通過(guò)特定的框架或容器,將其所需要的其他依賴(lài)組件進(jìn)行注入,主動(dòng)推入。

依賴(lài)注入是最早spring和Piconcontainer等提出,如今已經(jīng)是一個(gè)缺省主流模式,并擴(kuò)展到前端如Angular.js等等。

1. 依賴(lài)

如果在Class A中,有Class B的實(shí)例,則稱(chēng)Class A對(duì)Class B有一個(gè)依賴(lài)。例如下面類(lèi)ViewControllerA中用到一個(gè)ViewControllerB對(duì)象,我們就說(shuō)類(lèi)ViewControllerA對(duì)類(lèi)ViewControllerB有一個(gè)依賴(lài)。

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

仔細(xì)看這段代碼我們會(huì)發(fā)現(xiàn)存在一些問(wèn)題:

(1). 如果現(xiàn)在要改變ViewControllerB生成方式,如需要用initWithOrderid:(NSString * orderid)初始化vc,需要修改ViewControllerA代碼;

(2). 如果想測(cè)試不同ViewControllerB對(duì)象對(duì)ViewControllerA的影響很困難,因?yàn)閂iewControllerB 的初始化被寫(xiě)死在了ViewControllerA` 的構(gòu)造函數(shù)中;

(3). 如果[[ViewControllerB alloc] init]過(guò)程非常緩慢,單測(cè)時(shí)我們希望用已經(jīng)初始化好的ViewControllerB對(duì)象Mock掉這個(gè)過(guò)程也很困難。

2. 依賴(lài)注入

上面將依賴(lài)在構(gòu)造函數(shù)中直接初始化是一種Hard init方式,弊端在于兩個(gè)類(lèi)不夠獨(dú)立,不方便測(cè)試。我們還有另外一種Init方式,如下:

@interfaceViewControllerA()@property(nonatomic,readonly) ViewControllerB *vcB;@end@implementationViewControllerA// vcB是在ViewControllerA被創(chuàng)建之前被創(chuàng)建的并且作為參數(shù)傳進(jìn)來(lái),// 調(diào)用者如果想,還可以自定義。- (instancetype)initWithEngine:(ViewControllerB *)vcB? {? ? ...? ? _vcB = vcB;returnself;? }@end

上面代碼中,我們將vcB對(duì)象作為構(gòu)造函數(shù)的一個(gè)參數(shù)傳入。在調(diào)用ViewControllerA的構(gòu)造方法之前外部就已經(jīng)初始化好了vcB對(duì)象。像這種非自己主動(dòng)初始化依賴(lài),而通過(guò)外部來(lái)傳入依賴(lài)的方式,我們就稱(chēng)為依賴(lài)注入。

現(xiàn)在我們發(fā)現(xiàn)上面1中存在的兩個(gè)問(wèn)題都很好解決了,簡(jiǎn)單的說(shuō)依賴(lài)注入主要有兩個(gè)好處:

解耦,將依賴(lài)之間解耦。

因?yàn)橐呀?jīng)解耦,所以方便做單元測(cè)試,尤其是Mock測(cè)試。

那么問(wèn)題來(lái)了,如何學(xué)習(xí)Dependency Injection呢 ?iOS有關(guān)DI依賴(lài)注入的框架比較好用的有兩個(gè):Objection 和 Typhoon.下面就從幾個(gè)方便來(lái)介紹下這兩個(gè)框架

一:Objection 和 Typhoon這兩個(gè)框架有什么區(qū)別呢 其實(shí)這兩個(gè)框架各有優(yōu)勢(shì):

Objection框架,使用起來(lái)比較靈活,用法比較簡(jiǎn)單。示例代碼如下:

屬性注冊(cè):

@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")//屬性的依賴(lài)注入@synthesizeengine, brakes, awake;@end


方法注入:

@implementation Truckobjection_requires(@"engine", @"brakes")objection_initializer(truckWithMake:model:)//方法的依賴(lài)注入+ (instancetype)truckWithMake:(NSString *) make model: (NSString *)model {...}@end

2.對(duì)比來(lái)說(shuō)Typhoon的使用起來(lái)就比較規(guī)范,首先需要?jiǎng)?chuàng)建一個(gè)TyphoonAssembly的子類(lèi)。其需要注入的方法和屬性都需要寫(xiě)在這個(gè)統(tǒng)一個(gè)子類(lèi)中,當(dāng)然可以實(shí)現(xiàn)不同的子類(lèi)來(lái)完成不同的功能:

@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.當(dāng)然還有一些硬性的區(qū)別就是Typhoon現(xiàn)在已經(jīng)支持Swift。

4.兩者維護(hù)時(shí)間都超過(guò)2年以上。

Tythoon官方介紹的優(yōu)勢(shì):

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.

大體翻譯過(guò)來(lái):

1)非侵入性。不需要宏或XML。使用強(qiáng)大的ObjC運(yùn)行時(shí)儀器。2)沒(méi)有魔法字符串——支持IDE重構(gòu),完成和編譯時(shí)檢查。3)提供full-modularization和封裝的配置細(xì)節(jié)。讓你的架構(gòu)告訴一個(gè)故事。4)依賴(lài)關(guān)系中聲明的任何順序。(對(duì)人類(lèi)有意義的順序)。5)很容易有多個(gè)配置相同的基類(lèi)或協(xié)議。6)支持注射的視圖控制器和故事板集成。同時(shí)支持初始化器和屬性注入,以及生命周期管理。7)強(qiáng)大的內(nèi)存管理功能。提供預(yù)配置對(duì)象,沒(méi)有單件的內(nèi)存開(kāi)銷(xiāo)。8)優(yōu)秀的支持循環(huán)依賴(lài)。9)精益。占用很低,所以適合CPU和內(nèi)存受限的設(shè)備。10),功能強(qiáng)大,臺(tái)風(fēng)重總共只有3000行代碼。11)久經(jīng)沙場(chǎng),用于各種Appstore-featured應(yīng)用。

針對(duì)這兩個(gè)框架網(wǎng)上教程并不多,收集了一些比較有用的資料。最主要的用法還得看官方文檔分別在:ObjectionTyphoon

objc.io官網(wǎng)的博文 Dependency Injection 和 Typhoon原創(chuàng)大神(Graham Lee)的文章Dependency Injection, iOS and You不看后悔一輩子^_^

Objection是一個(gè)輕量級(jí)的依賴(lài)注入框架,受Guice的啟發(fā),Google Wallet 也是使用的該項(xiàng)目?!敢蕾?lài)注入」是面向?qū)ο缶幊痰囊环N設(shè)計(jì)模式,用來(lái)減少代碼之間的耦合度。通常基于接口來(lái)實(shí)現(xiàn),也就是說(shuō)不需要new一個(gè)對(duì)象,而是通過(guò)相關(guān)的控制器來(lái)獲取對(duì)象。2013年最火的php框架laravel就是其中的典型。

假設(shè)有以下場(chǎng)景:ViewControllerA.view里有一個(gè)button,點(diǎn)擊之后push一個(gè)ViewControllerB,最簡(jiǎn)單的寫(xiě)法類(lèi)似這樣:

-? (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];}

這樣寫(xiě)的一個(gè)問(wèn)題是,ViewControllerA需要import ViewControllerB,也就是對(duì)ViewControllerB產(chǎn)生了依賴(lài)。依賴(lài)的東西越多,維護(hù)起來(lái)就越麻煩,也容易出現(xiàn)循環(huán)依賴(lài)的問(wèn)題,而objection正好可以處理這些問(wèn)題。

實(shí)現(xiàn)方法是:先定義一個(gè)協(xié)議(protocol),然后通過(guò)objection來(lái)注冊(cè)這個(gè)協(xié)議對(duì)應(yīng)的class,需要的時(shí)候,可以獲取該協(xié)議對(duì)應(yīng)的object。對(duì)于使用方無(wú)需關(guān)心到底使用的是哪個(gè)Class,反正該有的方法、屬性都有了(在協(xié)議中指定)。這樣就去除了對(duì)某個(gè)特定Class的依賴(lài)。也就是通常所說(shuō)的「面向接口編程」

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] 獲取默認(rèn)的injector,這個(gè)injector已經(jīng)注冊(cè)過(guò)ViewControllerAProtocol了。

[2] 獲取ViewControllerAProtocol對(duì)應(yīng)的Object。

[3] 拿到VC后,設(shè)置它的某些屬性,比如這里的backgroundColor,因?yàn)樵赩iewControllerAProtocol里有定義這個(gè)屬性,所以不會(huì)有warning。

可以看到這里沒(méi)有引用ViewControllerA。再來(lái)看看這個(gè)ViewControllerAProtocol是如何注冊(cè)到injector中的,這里涉及到了Module,對(duì)Protocol的注冊(cè)都是在Module中完成的。Module只要繼承JSObjectionModule這個(gè)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方法里進(jìn)行的,這個(gè)方法在被添加到injector里時(shí)會(huì)被自動(dòng)觸發(fā)。

JSObjectionInjector *injector = [JSObjection defaultInjector]; // [1]injector = injector ? : [JSObjection createInjector]; // [2]injector = [injector withModule:[[ViewControllerAModule alloc] init]]; // [3][JSObjection setDefaultInjector:injector]; // [4]

[1] 獲取默認(rèn)的injector

[2] 如果默認(rèn)的injector不存在,就新建一個(gè)

[3] 往這個(gè)injector里注冊(cè)我們的Module

[4] 設(shè)置該injector為默認(rèn)的injector

這段代碼可以直接放到+ (void)load里執(zhí)行,這樣就可以避免在AppDelegate里import各種Module。

因?yàn)槲覀儫o(wú)法直接獲得對(duì)應(yīng)的Class,所以必須要在協(xié)議里定義好對(duì)外暴露的方法和屬性,然后該Class也要實(shí)現(xiàn)該協(xié)議。

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

通過(guò)Objection實(shí)現(xiàn)依賴(lài)注入后,就能更好地實(shí)現(xiàn)SRP(Single Responsibility Principle),代碼更簡(jiǎn)潔,心情更舒暢,生活更美好。

總體來(lái)說(shuō),這個(gè)lib還是挺靠譜的,已經(jīng)維護(hù)了兩年多,也有一些項(xiàng)目在用,對(duì)于提高開(kāi)發(fā)成員的效率也會(huì)有不少的幫助,可以考慮嘗試下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容