iOS依賴注入

什么是依賴注入呢?

依賴注入(DI)是一種非常流行的設(shè)計模式在許多的語言之中秽澳,比如說Java和C#,但是它似乎并沒有在Objective-C廣泛的被采用鸵荠,這篇文章的目的就是簡要的介紹一下在Objective-C中使用依賴注入的例子员帮。雖然代碼是用Objective-C寫的,但是也可以應(yīng)用到Swift邓梅。

依賴注入的概念很簡單:一個對象應(yīng)該被依賴而不是被創(chuàng)建。強烈建議閱讀Martin Fowler的excellent discussion on the subject作為背景閱讀月培。

注入可以通過對象的初始化(或者可以說構(gòu)造器)或者通過特性(setter)谷暮,這些就指的是構(gòu)造器注入和setter方法注入蔑匣。

構(gòu)造器注入

- (instancetype)initWithDependency1:(Dependency1 *)d1

dependency2:(Dependency2 *)d2;

Setter 注入

@property (nonatomic, retain) Dependency1 *dependency1;

@property (nonatomic, retain) Dependency2 *dependency2;

根據(jù)福勒的描述劣欢,首選構(gòu)造器注入,一般來說只有在構(gòu)造器注入不可行的情況下才會選擇settter注入裁良。在構(gòu)造器注入中凿将,你可以還是會使用@property來定義這些依賴,但是在接口中你應(yīng)該讓他們?yōu)橹蛔x价脾,

為什么使用依賴注入

依賴注入提供了一系列的好處牧抵,其中重要的是一下幾點:

依賴定義清晰可以很明顯的看出對象需要什么來進行操作,從而可以避免依賴類似(dependencies—like)侨把,全部變量消失(globals—disappear)犀变。

組合依賴注入提倡組合而不是繼承, 提高代碼的可重用性。

定制簡單當(dāng)創(chuàng)建一個對象的時候秋柄,可以輕松的定制對象的每一個部分來面對最糟糕的情況获枝。

所有權(quán)清晰特別是當(dāng)使用構(gòu)造函數(shù)注入,嚴格執(zhí)行對象的所有權(quán)規(guī)則骇笔,幫助建立一個有向無循環(huán)對象圖省店。

可測試性更重要的是,依賴注入提高了你的對象的可測性笨触。因為他們可以被非常簡單的創(chuàng)建通過初始化方法萨西,沒有隱藏的依賴關(guān)系需要管理。此外旭旭,可以簡單的模擬出依賴讓你關(guān)注于被測試的對象。

使用依賴注入

你的代碼可能還沒有使用依賴注入的設(shè)計模式來設(shè)計葱跋,但是很容易上手持寄。依賴注入的一個很好的方面是,你不需要采用“全或無”娱俺。相反稍味,你可以將它應(yīng)用到代碼的特定區(qū)域并從那里展開。

類型注入

首先荠卷,讓我們把類分成兩種:基本類和復(fù)合類模庐。基本類是沒有依賴的油宜,或只依賴于其他基礎(chǔ)類掂碱×耍基本類被繼承的可能性極低,因為它們的功能是明確的疼燥,不變的沧卢,不引用外部資源。Cocoa本身有很多的基本類醉者,如NSStringNSArray但狭,NSDictionaryNSNumber……。

復(fù)雜類則相反撬即,他們有復(fù)雜的依賴關(guān)系立磁,包括應(yīng)用級別的邏輯(可能被改變),或者訪問外部資源剥槐,比如磁盤唱歧,網(wǎng)絡(luò)或者一些全局的服務(wù)。在你應(yīng)用內(nèi)的大多數(shù)類都是復(fù)雜的才沧,包括大多數(shù)的控制器對象和模型對象迈喉。需要Cocoa類也食非常復(fù)雜的,比如說NSURLConnection或者UIViewController

判斷這個的最簡單方法就是拿起一個復(fù)雜類然后看它初始化其他復(fù)雜類的地方(搜索"alloc] init" or "new]")温圆,介紹了類的依賴注入挨摸,改變這個實例化的對象是通過作為一個類的初始化參數(shù)而不是類的實例化對象本身。

初始化中的依賴分配

讓我們來看一個例子岁歉,一個子對象(依賴)被初始化作為父對象的一部分得运。一般代碼如下:

@interface RCRaceCar ()

@property (nonatomic, readonly) RCEngine *engine;

@end

@implementation RCRaceCar

- (instancetype)init

{

...

// Create the engine. Note that it cannot be customized or

// mocked out without modifying the internals of RCRaceCar.

_engine = [[RCEngine alloc] init];

return self;

}

@end

依賴注入版本的有一些小小的不同

@interface RCRaceCar ()

@property (nonatomic, readonly) RCEngine *engine;

@end

@implementation RCRaceCar

// The engine is created before the race car and passed in

// as a parameter, and the caller can customize it if desired.

- (instancetype)initWithEngine:(RCEngine *)engine

{

...

_engine = engine;

return self;

}

@end

依賴惰性初始化

一些對象有時會在很后面才會被用到,有時甚至在初始化之后不會被用到锅移,舉個例子(在依賴注入之前)也許看起來是這樣子的:

@interface RCRaceCar ()

@property (nonatomic) RCEngine *engine;

@end

@implementation RCRaceCar

- (instancetype)initWithEngine:(RCEngine *)engine

{

...

_engine = engine;

return self;

}

- (void)recoverFromCrash

{

if (self.fire != nil) {

RCFireExtinguisher *fireExtinguisher = [[RCFireExtinguisher alloc] init];

[fireExtinguisher extinguishFire:self.fire];

}

}

@end

在這種情況下熔掺,賽車希望不會出事,而我們也不需要使用滅火器非剃。由于需要該對象的機會很低置逻,我們不想每一個賽車的創(chuàng)建的時候慢下來。另外备绽,如果我們的賽車需要從多個事故恢復(fù)券坞,就需要創(chuàng)建多個滅火器。針對這些情況肺素,我們可以使用工廠恨锚。

工廠標(biāo)準(zhǔn)的Objective-C塊不需要參數(shù)和返回一個對象的實例。一個對象可以控制當(dāng)其依賴的是使用這些塊而不需要詳細了解如何創(chuàng)建倍靡。

這里是一個例子使用了依賴注入和工廠模式來創(chuàng)建滅火器猴伶。

typedef RCFireExtinguisher *(^RCFireExtinguisherFactory)();

@interface RCRaceCar ()

@property (nonatomic, readonly) RCEngine *engine;

@property (nonatomic, copy, readonly) RCFireExtinguisherFactory fireExtinguisherFactory;

@end

@implementation RCRaceCar

- (instancetype)initWithEngine:(RCEngine *)engine

fireExtinguisherFactory:(RCFireExtinguisherFactory)extFactory

{

...

_engine = engine;

_fireExtinguisherFactory = [extFactory copy];

return self;

}

- (void)recoverFromCrash

{

if (self.fire != nil) {

RCFireExtinguisher *fireExtinguisher = self.fireExtinguisherFactory();

[fireExtinguisher extinguishFire:self.fire];

}

}

@end

工廠模式在我們創(chuàng)建未知數(shù)量的依賴的時候也是非常有用的,即使是在初始化期間,例子如下:

@implementation RCRaceCar

- (instancetype)initWithEngine:(RCEngine *)engine

transmission:(RCTransmission *)transmission

wheelFactory:(RCWheel *(^)())wheelFactory;

{

self = [super init];

if (self == nil) {

return nil;

}

_engine = engine;

_transmission = transmission;

_leftFrontWheel = wheelFactory();

_leftRearWheel = wheelFactory();

_rightFrontWheel = wheelFactory();

_rightRearWheel = wheelFactory();

// Keep the wheel factory for later in case we need a spare.

_wheelFactory = [wheelFactory copy];

return self;

}

@end

避免麻煩的配置

如果對象不應(yīng)該在其他對象分配他挎,他們在哪里分配筝尾?是不是所有的這些依賴關(guān)系很難配置?大多不都是一樣的嗎雇盖?這些問題的解決在于類方便的初始化(想一想+[NSDictionary dictionary])忿等。我們會把我們的對象圖的配置移出我們正常的對象,把他們抽象出來崔挖,測試贸街,業(yè)務(wù)邏輯。

在我們添加簡單初始化方法之前狸相,先確認它是有必要的薛匪。如果一個對象只有少量的參數(shù)在初始化方法之中,這些參數(shù)沒有規(guī)律脓鹃,那么簡單初始化方法是沒有必要的逸尖。調(diào)用者應(yīng)該使用標(biāo)準(zhǔn)初始化方法。

我們需要從四個地方搜集依賴來配置我們的對象:

沒有明確默認值的變量這些包括布爾類型和數(shù)字類型的有可能在每個實例中是不同的瘸右。這些變量應(yīng)該在簡單初始化方法的參數(shù)中娇跟。

共享對象這些應(yīng)該在簡單初始化方法中作為參數(shù)(例如電臺頻率),這些對象先前可能被訪問作為一個單例或者通過父指針太颤。

新創(chuàng)建的對象如果我們的對象不與另一個對象分享這種依賴關(guān)系苞俘,合作者對象應(yīng)該是新實例化的類中簡單初始化。這些都是以前分配的對象直接在該對象的實現(xiàn)龄章。

系統(tǒng)單例Cocoa提供了很多單例可以直接被訪問吃谣,例如[NSFileManager defaultManager],那里有一個明確的目的只需要一個實例被使用在應(yīng)用中做裙,還有很多單例在系統(tǒng)中岗憋。

一個對于賽車的簡單初始化看起來如下:

+ (instancetype)raceCarWithPitRadioFrequency:(RCRadioFrequency *)frequency;

{

RCEngine *engine = [[RCEngine alloc] init];

RCTransmission *transmission = [[RCTransmission alloc] init];

RCWheel *(^wheelFactory)() = ^{

return [[RCWheel alloc] init];

};

return [[self alloc] initWithEngine:engine

transmission:transmission

pitRadioFrequency:frequency

wheelFactory:wheelFactory];

}

你的類的簡單初始化方法應(yīng)該是最合適的。一個經(jīng)常被用到的(可重用)的配置在一個.m文件中锚贱,而一個特定的配置文件應(yīng)該在在@interface RaceCar (FooConfiguration)的類別文件中比如fooRaceCar仔戈。

系統(tǒng)單例

在Cocoa的許多對象中,只有一種實例將一直存在拧廊。 例如[UIApplication sharedApplication],[NSFileManager defaultManager],[NSUserDefaults standardUserDefaults], 和[UIDevice currentDevice]杂穷,如果一個對象對他們存在依賴,那么應(yīng)該在初始化的參數(shù)中包含他們卦绣,即使也許在你的代碼中只有一個實例,你的測試可能要模擬實例或創(chuàng)建一個實例每個測試避免測試相互依存飞蚓。

建議避免創(chuàng)建全局引用單例滤港,而是創(chuàng)建一個對象的單個實例當(dāng)它第一次被需要的時候并把它注入到所有依賴它的對象中去。

不可變構(gòu)造器

偶爾在某個類的構(gòu)造器或者初始化方法不能被改變或者調(diào)用的時候,我們可以使用setter注入溅漾。例如:

// An example where we can't directly call the the initializer.

RCRaceTrack *raceTrack = [objectYouCantModify createRaceTrack];

// We can still use properties to configure our race track.

raceTrack.width = 10;

raceTrack.numberOfHairpinTurns = 2;

setter注入允許你配置對象山叮,但是它也引入了易變性,你必須進行測試和處理添履,幸運的是屁倔,有兩個場景導(dǎo)致初始化方法不能改變和調(diào)用,他們都是可以避免的暮胧。

類注冊

使用類注冊的工廠模式意味著不能修改初始化方法锐借。

NSArray *raceCarClasses = @[

[RCFastRaceCar class],

[RCSlowRaceCar class],

];

NSMutableArray *raceCars = [[NSMutableArray alloc] init];

for (Class raceCarClass in raceCarClasses) {

// All race cars must have the same initializer ("init" in this case).

// This means we can't customize different subclasses in different ways.

[raceCars addObject:[[raceCarClass alloc] init]];

}

一個簡單的替代方法就是使用工廠塊而不是類的聲明列表。

typedef RCRaceCar *(^RCRaceCarFactory)();

NSArray *raceCarFactories = @[

^{ return [[RCFastRaceCar alloc] initWithTopSpeed:200]; },

^{ return [[RCSlowRaceCar alloc] initWithLeatherPlushiness:11]; }

];

NSMutableArray *raceCars = [[NSMutableArray alloc] init];

for (RCRaceCarFactory raceCarFactory in raceCarFactories) {

// We now no longer care which initializer is being called.

[raceCars addObject:raceCarFactory()];

}

故事版

故事提供了一個方便的方式來設(shè)計你的用戶界面往衷,但也帶來一些問題的時候钞翔。特別是依賴注入,實例化初始視圖控制器在故事板中不允許你選擇初始化方法席舍。同樣布轿,當(dāng)下面的事情就是在故事板中定義,目標(biāo)視圖控制器被實例化也不允許你指定的初始化方法来颤。

解決這個問題的辦法是避免使用故事板汰扭。這似乎是一個極端的解決方案,但是我們發(fā)現(xiàn)故事板有其他問題在大型團隊的開發(fā)中福铅。此外萝毛,沒有必要失去大部分故事版的好處。xibs提供所有相同的好處和故事板本讥,除了segues珊泳,但仍然讓你自定義初始化。

公共和私有

依賴注入鼓勵你暴露更多的對象在你的公共接口拷沸。如前所述色查,這有許多好處。但當(dāng)你構(gòu)建框架撞芍,它會明顯的膨脹公共API秧了。使用依賴注入之前,公共對象A可能使用私有對象B(這反過來使用私有對象C)序无,但對象B和C分別從未曝光的外部框架验毡。使用依賴注入的對象A會使對象B暴露在公共初始化方法,對象B反過來將對象C暴露在公共初始化方法帝嗡。晶通。

// In public ObjectA.h.

@interface ObjectA

// Because the initializer uses a reference to ObjectB we need to

// make the Object B header public where we wouldn't have before.

- (instancetype)initWithObjectB:(ObjectB *)objectB;

@end

@interface ObjectB

// Same here: we need to expose ObjectC.h.

- (instancetype)initWithObjectC:(ObjectC *)objectC;

@end

@interface ObjectC

- (instancetype)init;

@end

對象B和對象C都是具體的實現(xiàn),但你并不需要框架的使用者來擔(dān)心他們哟玷,我們可以通過協(xié)議的方式來解決狮辽。

@interface ObjectA

- (instancetype)initWithObjectB:(id )objectB;

@end

// This protocol exposes only the parts of the original ObjectB that

// are needed by ObjectA. We're not creating a hard dependency on

// our concrete ObjectB (or ObjectC) implementation.

@protocol ObjectB

- (void)methodNeededByObjectA;

@end

結(jié)束語

依賴注入在 Objective-C(同樣在Swift),合理的使用會使你的代碼更加易讀、易測試和易維護喉脖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椰苟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子树叽,更是在濱河造成了極大的恐慌舆蝴,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件题诵,死亡現(xiàn)場離奇詭異洁仗,居然都是意外死亡,警方通過查閱死者的電腦和手機仇轻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門京痢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篷店,你說我怎么就攤上這事祭椰。” “怎么了疲陕?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵方淤,是天一觀的道長。 經(jīng)常有香客問我蹄殃,道長携茂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任诅岩,我火速辦了婚禮讳苦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吩谦。我一直安慰自己鸳谜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布式廷。 她就那樣靜靜地躺著咐扭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滑废。 梳的紋絲不亂的頭發(fā)上蝗肪,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音蠕趁,去河邊找鬼薛闪。 笑死,一個胖子當(dāng)著我的面吹牛俺陋,可吹牛的內(nèi)容都是我干的逛绵。 我是一名探鬼主播怀各,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼术浪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寿酌,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胰苏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后醇疼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硕并,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年秧荆,在試婚紗的時候發(fā)現(xiàn)自己被綠了倔毙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡乙濒,死狀恐怖陕赃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颁股,我是刑警寧澤么库,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站甘有,受9級特大地震影響诉儒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亏掀,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一忱反、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滤愕,春花似錦温算、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宇智,卻和暖如春蔓搞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背随橘。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工喂分, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人机蔗。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓蒲祈,卻偏偏與公主長得像甘萧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子梆掸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • iOS 依賴注入 最近讀項目代碼的總結(jié)扬卷! 什么是依賴呢,會有什么問題呢酸钦?平時寫代碼怪得,這種依賴的方式太常見了。 看代...
    楊柳小易閱讀 4,159評論 0 8
  • 源碼 依賴注入(Dependency Injection)這個詞卑硫,源于java徒恋,但在Cocoa框架中也是十分常見的...
    秋刀生魚片閱讀 2,676評論 4 12
  • dependency injection 關(guān)于IOS依賴注入那些事 本文介紹的是另一個屎上最牛叉的ios開發(fā)新框架...
    十三億少女夢丶閱讀 9,737評論 1 44
  • 要使用工具, 首先還是先來了解一下為什么要使用它? 而這里有一篇很好的文章說明為什么要進行依賴注入, 以及一些相關(guān)...
    貘鳴閱讀 6,188評論 3 12
  • 依賴注入(Dependency Injection) 依賴注入最大的特點就是:幫助我們開發(fā)出松散耦合(loose ...
    小李龍彪閱讀 2,402評論 1 0