iOS項目依賴注入簡介

來源:互聯網
上載者:User

標籤:

依賴注入(Dependency Injection)

依賴注入 最大的特點就是:協助我們開發出鬆散耦合(loose coupled)、可維護、可測試的代碼和程式。這條原則的做法是大家熟知的面向介面,或者說是面向抽象編程。 眾所周知該編程思想在各大語言中都有體現如jave、 C++、 PHP 以及 .net中。當然設計模式的廣泛程度遠遠大於這些,iOS 當然也不例外。 本文主要介紹本人在學習Dependency Injection的時候的學習過程以及對一些學習資料的總結,主要介紹iOS中的兩大架構Objection 和 Typhoon 。 在 Android上比較流行的有 RoboGuice 和 Dagger 等.

什麼是依賴注入(Dependency Injection)? 

依賴注入(Dependency Injection) 是一個將行為從依賴中分離的技術,簡單地說,它允許開發人員定義一個方法函數依賴於外部其他各種互動,而不需要編碼如何獲得這些外部互動的執行個體。 這樣就在各種組件之間解耦,從而獲得乾淨的代碼,相比依賴的寫入程式碼, 一個組件只有在運行時才調用其所需要的其他組件,因此在代碼運行時,通過特定的架構或容器,將其所需要的其他相依元件進行注入,主動推入。

依賴注入是最早SpringPiconcontainer等提出,如今已經是一個預設主流模式,並擴充到前端如Angular.js等等。

1. 依賴

如果在 Class A中,有 Class B的執行個體,則稱 Class A對 Class B 有一個依賴。例如下面類 ViewControllerA 中用到一個 ViewControllerB 對象,我們就說類 ViewControllerA 對類 ViewControllerB 有一個依賴。

 

 #import "ViewControllerB.h"@implementation ViewControllerA- (void)buttonTapped{    ViewControllerB *vc = [[ViewControllerB alloc] init];    [self.navigationController pushViewController: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 方式,如下:

@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測試。

那麼問題來了,如何學習Dependency Injection呢 ?iOS有關DI依賴注入的架構比較好用的有兩個:Objection 和 Typhoon.下面就從幾個方便來介紹下這兩個架構一:Objection 和 Typhoon這兩個架構有什麼區別呢 其實這兩個架構各有優勢:
  1. 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 Carobjection_requires(@"engine", @"brakes") //屬性的依賴注入@synthesize engine, 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<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;

 

  • [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裡執行,這樣就可以避免在AppDelegateimport各種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還是挺靠譜的,已經維護了兩年多,也有一些項目在用,對於提高開發成員的效率也會有不少的協助,可以考慮嘗試下

iOS項目依賴注入簡介

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.