依賴注入
最大的特色就是:幫助咱們開發出鬆散耦合(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仍是挺靠譜的,已經維護了兩年多,也有一些項目在用,對於提升開發成員的效率也會有很多的幫助,能夠考慮嘗試下。