什么是依賴
對象A持有了對象B盖袭,我們就可以說對象A依賴對象B失暂,或者對象B是對象A的一個依賴。一個對象需要的依賴越多鳄虱,該對象耦合度越高弟塞,也就越難解藕。
/************** A對象 **************/
#import "BObject.h"
@interface AObject : NSObject
@property (strong, nonatomic) BObject* bj;
-(void)dosomething;
@end
#import "AObject.h"
@implementation AObject
-(instancetype)init {
self = [super init];
if (self) {
self.bj = [[BObject alloc] initWithName:@"LOLITA"];
}
return self;
}
-(void)dosomething{
NSLog(@"I am %@.", self.bj.name);
[self.bj running];
}
@end
/************** B對象 **************/
@interface BObject : NSObject
@property (copy, nonatomic, readonly) NSString* name;
@property (assign, nonatomic, readonly) NSInteger age;
-(instancetype)initWithName:(NSString*)name;
-(instancetype)initWithName:(NSString*)name age:(NSInteger)age;
-(void)running;
@end
#import "BObject.h"
@interface BObject ()
@property (copy, nonatomic, readwrite) NSString* name;
@property (assign, nonatomic, readwrite) NSInteger age;
@end
@implementation BObject
-(instancetype)initWithName:(NSString*)name{
return [self initWithName:name age:26];
}
-(instancetype)initWithName:(NSString*)name age:(NSInteger)age{
self = [super init];
if (self) {
self.name = name;
self.age = age;
}
return self;
}
-(void)running{
NSLog(@"running...");
}
@end
存在的問題
仔細觀察上述代碼拙已,我們會發(fā)現(xiàn)一些問題:
- 如果現(xiàn)在想要更換 B 的構(gòu)造方式决记,如使用構(gòu)造方法
-initWithName:age:
,那么我們需要修改 A 類中的代碼倍踪; - 如果想要測試多種的 B 對 A 的影響變得很難系宫,因為 A 中寫死了 B 的構(gòu)造方法索昂。
什么是依賴注入?
上面那種將依賴直接自己初始化是一種硬編碼的方式扩借,弊端在于兩個類不夠獨立椒惨,不方便測試,我們對 A 進行一點修改:
#import "BObject.h"
@interface AObject : NSObject
@property (strong, nonatomic) BObject* bj;
-(instancetype)initWithBObject:(BObject*)bj;
-(void)dosomething;
@end
#import "AObject.h"
@implementation AObject
-(instancetype)initWithBObject:(BObject *)bj {
self = [super init];
if (self) {
self.bj = bj;
}
return self;
}
-(void)dosomething{
NSLog(@"I am %@.", self.bj.name);
[self.bj running];
}
@end
看起來并沒有多少變化潮罪,上述修改最大的特點是我們將 B 作為 A 的構(gòu)造函數(shù)的一部分傳入康谆,在調(diào)用 A 的構(gòu)造方法之前就已經(jīng)初始化好的 B。像這種非自身主動創(chuàng)建依賴嫉到,而是通過外部傳入的方式沃暗,我們就稱為依賴注入。
【依賴注入】(Dependency Injection) 是面向?qū)ο缶幊痰囊环N設(shè)計模式何恶,用來減少代碼之間的耦合度描睦。通常基于接口來實現(xiàn)导而,也就是說:不需要親自new一個對象忱叭,而是通過相關(guān)的控制器來獲取對象。
依賴注入的好處
- 方便地使用新對象取代舊的對象今艺。如果從類的一種實現(xiàn)更改為另外一種實現(xiàn)韵丑,只需更改一個聲明;
- 消除緊密耦合虚缎;
- 類更容易測試撵彻;
- 關(guān)注點分離和面向接口。很容易看出每個類需要什么才能完成工作实牡,讓團隊成員之間的合作變得更容易陌僵。
注入的方式
依賴注入的方式大體分為兩種,相信你們都能想到:構(gòu)造方法注入和屬性注入 创坞。
- 構(gòu)造方法注入
上面通過構(gòu)造器注入依賴對象的方式就是一種典型的方法注入碗短。外部使用的形式如下:
BObject* bj = [[BObject alloc] initWithName:@"xiaoming"];
AObject* ob = [[AObject alloc] initWithBObject:bj];
[ob dosomething];
- 屬性注入
屬性注入也是一種常見的注入方式,通過表現(xiàn)就是調(diào)用對象的相關(guān)屬性進行賦值:
AObject* ob = [[AObject alloc] init];
ob.bj = [[BObject alloc] initWithName:@"xiaoming"];
[ob dosomething];
除此之外题涨,你可能還聽說過其他的注入方式偎谁,比如工廠注入、延遲注入(塊)等纲堵,這些大體上都是上述兩種的變體巡雨。不管是何種注入,需要遵循的原則就是外部創(chuàng)建對象的依賴席函,而不是自身主動創(chuàng)建铐望,這樣的好處就是幫助我們開發(fā)出松散耦合(loose coupled)、可維護、可測試的代碼和程序正蛙,這種原則就是大家熟知的面向接口炕舵,或者說是面向抽象編程。
下面我們主要介紹 iOS 依賴注入的兩大框架 Objection 和 Typhoon 的使用跟畅,幫助大家更好的理解依賴注入以及面向接口編程的思想咽筋。
Objection
簡介
Objection 是適用于 MacOS X 和 iOS 的 Objective-C 的輕量級依賴注入框架。Objection 旨在減輕維護大型 XML 容器或手動構(gòu)造對象的需求徊件。
基本用法
** 注入器 Injector **
上面關(guān)于依賴注入一節(jié)講過 不需要親自new一個對象奸攻,而是通過相關(guān)的控制器來獲取對象
。Injector
就是所謂的控制器虱痕,我們用它來配置和管理需要進行依賴注入的對象睹耐。
// 啟用默認的注入器
JSObjectionInjector* injector = [JSObjection defaultInjector];
// 如果不存在,則創(chuàng)建一個
injector = injector ? : [JSObjection createInjector];
[JSObjection setDefaultInjector:injector];
// 通過注入器獲取一個對象
AObject* aj = [injector getObject:AObject.class];
// injector[AObject.class]; // 支持下標(biāo)獲取
// [injector objectForKeyedSubscript:AObject.class];
NSLog(@"%@",aj);
1.0 一些宏的使用
上面的示例演示了默認注入器Injector
的創(chuàng)建以及使用Class
獲取一個實例部翘。注意硝训,這里的defaultInjector
并不是系統(tǒng)常見的單例,它僅僅是一個常量新思,需要手動初始化窖梁,可以被重置。
1.1 屬性注入
上面示例你是否發(fā)現(xiàn)夹囚,雖然通過注入器獲取到了實例纵刘,但是該實例的依賴就為nil
,不要著急荸哟,我們一步一步來介紹假哎。
objection_requires
和objection_requires_sel
這兩個宏用來屬性注入,兩者的區(qū)別是前者使用屬性字符(不存在或者拼寫錯誤編譯器不會提示)鞍历,后者采用方法選擇器SEL
的形式舵抹,如果getter不存在,會發(fā)出警告劣砍,因此建議采用SEL
形式比較好惧蛹。
我們來修改一下 A 中的代碼:
#import "BObject.h"
@interface AObject : NSObject
@property (strong, nonatomic) BObject* bj;
@end
#import "AObject.h"
#import <Objection/Objection.h>
@implementation AObject
//objection_requires(@"bj")
objection_requires_sel(@selector(bj))
-(void)dosomething{
NSLog(@"I am %@.", self.bj.name);
}
@end
然后我們在合適的位置來獲取 A 對象。這里關(guān)于注入器的創(chuàng)建設(shè)置部分你可以放在應(yīng)用啟動的地方秆剪,這里不再演示出來赊淑。
JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* aj = [injector getObject:AObject.class];
NSLog(@"%@ - %@",aj, aj.bj);
這樣,你通過屬性注入宏以及注入器輕松的獲取到了 A 對象并為 A 對象綁定了依賴 B 對象仅讽。
注意,發(fā)生拼寫錯誤都會掛掉哦钾挟。
1.2 構(gòu)造方法注入
和屬性注入一樣洁灵,方法注入同樣有兩個宏可以使用:objection_initializer
和objection_initializer_sel
。這次我們通過構(gòu)造器來進行依賴注入:
@interface AObject : NSObject
@property (strong, nonatomic) BObject* bj;
-(instancetype)initWithBObject:(BObject*)bj; // 實例方法
+(AObject*)objectWithBObject:(BObject*)bj; // 類方法
@end
@implementation AObject
objection_initializer_sel(@selector(initWithBObject:)) // 該宏只需且只能出現(xiàn)一次
//objection_initializer_sel(@selector(objectWithBObject:))
-(instancetype)initWithBObject:(BObject*)bj{
self = [super init];
if (self) {
self.bj = bj;
}
return self;
}
+(AObject *)objectWithBObject:(BObject *)bj{
AObject* aj = AObject.new;
aj.bj = bj;
return aj;
}
-(void)dosomething{
NSLog(@"I am %@.", self.bj.name);
}
@end
在合適的地方通過注入器獲取 A :
JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* aj =
// 參數(shù)使用數(shù)組的形式傳入
[injector getObject:AObject.class argumentList:@[BObject.new]];
// 參數(shù)通過不定參數(shù)傳入
// [injector getObjectWithArgs:AObject.class, BObject.new, nil];
NSLog(@"%@ - %@",aj, aj.bj);
1.3 非侵入式方法注入
在1.2節(jié)中,我們使用了宏綁定了構(gòu)造方法徽千,這次我們采用非侵入式的方式獲取 A 對象苫费。
我們先刪除之前的方法注入的宏,然后通過注入器直接指定某個構(gòu)造器双抽,傳入對應(yīng)的參數(shù)來獲取 A 對象:
JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* aj = [injector getObject:AObject.class initializer:@selector(initWithBObject:) argumentList:@[BObject.new]];
NSLog(@"%@ - %@",aj, aj.bj);
這種方式似乎并不比使用原生的構(gòu)造方法創(chuàng)建 A 對象來的簡潔百框。
1.4 作用域
JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* ob1 = [injector getObject:AObject.class];
AObject* ob2 = [injector getObject:AObject.class];
NSLog(@"%@ - %@", ob1, ob2);
輸出:
<AObject: 0x6000024d03a0> - <AObject: 0x6000024c0190>
一般情況下,我們每次從注入器中獲取的對象都是不同的牍汹,大部分情況下铐维,這個沒什么可說的。在你的數(shù)據(jù)模型中也可以加上宏 objection_register
慎菲,該宏是將當(dāng)前類注冊到注入器中嫁蛇,當(dāng)然,你不寫露该,你依舊能從注入器中獲取到實例對象睬棚,我們找到該宏:
#define objection_register(value) \
+ (void)initialize { \
if (self == [value class]) { \
[JSObjection registerClass:[value class] scope: JSObjectionScopeNormal]; \
} \
}
可以看到,該宏實際上給數(shù)據(jù)模型類中添加類一個初始化方法解幼,并將自身的作用域 scope
設(shè)置為普通類型抑党,該類型是個枚舉:
typedef enum {
JSObjectionScopeNone = -1,
JSObjectionScopeNormal,
JSObjectionScopeSingleton
} JSObjectionScope;
這個枚舉中有一個單例枚舉,對應(yīng)的宏為 objection_register_singleton
撵摆,我們使用該宏新荤,看看有什么效果:
#import "AObject.h"
@implementation AObject
objection_register_singleton(self)
-(instancetype)initWithBObject:(BObject *)bj {
self = [super init];
if (self) {
self.bj = bj;
}
return self;
}
-(void)dosomething{
NSLog(@"I am %@.", self.bj.name);
[self.bj running];
}
@end
同樣的,我們運行下面的代碼:
JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* ob1 = [injector getObject:AObject.class];
AObject* ob2 = [injector getObject:AObject.class];
NSLog(@"%@ - %@", ob1, ob2);
輸出:
<AObject: 0x6000035602c0> - <AObject: 0x6000035602c0>
我們發(fā)現(xiàn)台汇,再次從注入器中獲取到的對象是同一個了苛骨,該對象作用域類似于系統(tǒng)的單例,但是它并不是真正的單例苟呐,僅僅是一個全局變量痒芝,并且其作用域可以再次被修改。
2.0 模塊
模塊是一組綁定牵素,這些綁定為注入器提供了額外的配置信息严衬。 這對于將外部依賴關(guān)系以及將協(xié)議綁定到類或?qū)嵗貏e有用。
我們的 Module 需要繼承自
JSObjectionModule
笆呆。
2.1 實例和協(xié)議的綁定
2.1.1 綁定實例
我們創(chuàng)建一個名為 MyModule
的對象请琳,繼承自 JSObjectionModule
。在方法 -configure
中完成配置赠幕。
#import "MyModule.h"
#import "AObject.h"
#import "BObject.h"
@implementation MyModule
-(void)configure {
// 通過 class 綁定某個實例對象俄精,該對象通常是該類的實例
AObject* ob = AObject.new;
NSLog(@"綁定的對象:%@",ob);
[self bind:ob toClass:AObject.class];
}
@end
注入器不再使用默認的,而是通過我們自己的模塊來創(chuàng)建:
// 啟用自己的模塊創(chuàng)建注入器
JSObjectionInjector* injector = [JSObjection createInjector:MyModule.new];
[JSObjection setDefaultInjector:injector];
在某個合適的時機通過類名來獲取該實例對象:
JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* ob1 = [injector getObject:AObject.class];
AObject* ob2 = [injector getObject:AObject.class];
NSLog(@"%@ - %@", ob1, ob2);
控制臺輸出:
綁定的對象:<AObject: 0x600000e5c720>
<AObject: 0x600000e5c720> - <AObject: 0x600000e5c720>
通過對比發(fā)現(xiàn)榕堰,確實是同一個實例竖慧。當(dāng)然你也可以將實例換成其他的嫌套,就像下面這樣:
// 通過 class 綁定某個實例對象
BObject* bj = [[BObject alloc] initWithName:@"xiaoming"];
NSLog(@"綁定的對象:%@",bj);
[self bind:bj toClass:AObject.class];
JSObjectionInjector* injector = [JSObjection defaultInjector];
BObject* ob1 = [injector getObject:AObject.class];
BObject* ob2 = [injector getObject:AObject.class];
NSLog(@"%@ %@ - %@ %@", ob1, ob1.name, ob2, ob2.name);
控制臺輸出:
綁定的對象:<BObject: 0x600002a57aa0>
<BObject: 0x600002a57aa0> xiaoming - <BObject: 0x600002a57aa0> xiaoming
通過對比發(fā)現(xiàn),我們通過類名獲取到了之前綁定的實例圾旨。到這里踱讨,我們大可以將注入器 injector
的使用類比為字典容器的使用,通過 key
將 value
進行綁定砍的,然后通過 key
獲取實例的過程痹筛。只不過,這個“字典容器”的功能更加的豐富廓鞠。
2.1.2 綁定協(xié)議
在面向接口式編程時帚稠,我們通常只關(guān)心我的需求是什么,誰來實現(xiàn)并不重要诫惭。這樣做的好處就是在多人合作開發(fā)時翁锡,你并不需要落實到某個實例(這通常也不是很重要),你需要做的就是拋出你的需求夕土,然后關(guān)心有了這些“實現(xiàn)”去完成接下來要做的事情即可馆衔。
例如,跳轉(zhuǎn)某個頁面怨绣,你需要別人提供一些信息角溃,你大可拋出需求做成接口協(xié)議,然后使用協(xié)議中的東西篮撑,別人也不需要關(guān)心你通過何種方式减细、何種類去實現(xiàn)該協(xié)議的(后期更改協(xié)議的實現(xiàn)很方便),甚至對方也需要該協(xié)議實現(xiàn)某個功能赢笨,同樣可以放在協(xié)議中未蝌。
我們創(chuàng)建一個 B 頁面和一個對應(yīng)的協(xié)議,該協(xié)議需要提供一個背景色以及一段字符串茧妒。
@protocol BViewControllerProtocol <NSObject>
@property (strong, nonatomic) UIColor *bgColor;
@property (strong, nonatomic) NSString *others;
@end
在 B 頁面遵循該協(xié)議萧吠,并使用這些數(shù)據(jù):
#import "BViewControllerProtocol.h"
@interface BViewController : UIViewController <BViewControllerProtocol>
@end
@implementation BViewController
@synthesize bgColor,others;
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = self.bgColor;
NSLog(@"%@",self.others);
}
@end
接著,我們來配置模塊桐筏,我們將該協(xié)議綁定到具體的實現(xiàn)類上:
-(void)configure {
// 通過接口協(xié)議纸型,將符合BViewControllerProtocol的類進行綁定
[self bindClass:BViewController.class toProtocol:@protocol(BViewControllerProtocol)];
}
在某個合適的時機,我們通過注入器獲取符合該協(xié)議的實例對象梅忌。
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
JSObjectionInjector* injector = [JSObjection defaultInjector];
UIViewController <BViewControllerProtocol>* nextCtrl = [injector getObject:@protocol(BViewControllerProtocol)];
nextCtrl.bgColor = UIColor.redColor;
nextCtrl.others = @"something...";
[self.navigationController pushViewController:nextCtrl animated:YES];
}
這樣狰腌,外部無須關(guān)注是誰實現(xiàn)了該協(xié)議,只要符合該協(xié)議接口的任何實例就行牧氮,而且在后期更換協(xié)議的實現(xiàn)時琼腔,外部也無須進行任何改動。
2.1.3 注意事項
-
-bind:
和-bindClass:
方法 -bind:
和 -bindClass:
的區(qū)別就是蹋笼,前者綁定實例是全局實例展姐,即通過注入器會獲取同一個實例躁垛,就是你所綁定的實例剖毯,后者綁定局部實例圾笨,即每次通過注入器獲取的對象都是不同的,因為你綁定的是類逊谋。
-bind:
-(void)configure {
BViewController* ob1 = BViewController.new;
NSLog(@"%@",ob1);
[self bind:ob1 toProtocol:@protocol(BViewControllerProtocol)];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
JSObjectionInjector* injector = [JSObjection defaultInjector];
UIViewController <BViewControllerProtocol>* nextCtrl1 = [injector getObject:@protocol(BViewControllerProtocol)];
UIViewController <BViewControllerProtocol>* nextCtrl2 = [injector getObject:@protocol(BViewControllerProtocol)];
NSLog(@"first:%@ - second:%@",nextCtrl1, nextCtrl2);
}
控制臺輸出:
<BViewController: 0x7f9a6bb06d30>
first:<BViewController: 0x7f9a6bb06d30> - second:<BViewController: 0x7f9a6bb06d30>
-bindClass:
-(void)configure {
[self bindClass:BViewController.class toProtocol:@protocol(BViewControllerProtocol)];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
JSObjectionInjector* injector = [JSObjection defaultInjector];
UIViewController <BViewControllerProtocol>* nextCtrl1 = [injector getObject:@protocol(BViewControllerProtocol)];
UIViewController <BViewControllerProtocol>* nextCtrl2 = [injector getObject:@protocol(BViewControllerProtocol)];
NSLog(@"first:%@ - second:%@",nextCtrl1, nextCtrl2);
}
控制臺輸出:
first:<BViewController: 0x7fbedff0b300> - second:<BViewController: 0x7fbedfd06c20>
- 綁定相同的
class
/protocol
當(dāng)你綁定相同的 class
/protocol
時擂达,之前的綁定會被覆蓋,就像下面這樣的情況胶滋。覆蓋的好處就是板鬓,我們可以在某些時刻修改相應(yīng)實現(xiàn),后面會提到究恤。
-(void)configure {
[self bindClass:BViewController.class toProtocol:@protocol(BViewControllerProtocol)];
BViewController* ob1 = BViewController.new;
[self bind:ob1 toProtocol:@protocol(BViewControllerProtocol)];
}
如果你不希望被覆蓋俭令,你可以使用 name
進行區(qū)分:
-(void)configure {
BViewController* ob1 = BViewController.new;
[self bind:ob1 toProtocol:@protocol(BViewControllerProtocol) named:@"first"];
BViewController* ob2 = BViewController.new;
[self bind:ob2 toProtocol:@protocol(BViewControllerProtocol) named:@"second"];
NSLog(@"first:%@ - second:%@",ob1, ob2);
}
獲取:
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
JSObjectionInjector* injector = [JSObjection defaultInjector];
UIViewController <BViewControllerProtocol>* nextCtrl1 = [injector getObject:@protocol(BViewControllerProtocol) named:@"first"];
UIViewController <BViewControllerProtocol>* nextCtrl2 = [injector getObject:@protocol(BViewControllerProtocol) named:@"second"];
NSLog(@"first ctrl:%@ - second ctrl:%@",nextCtrl1, nextCtrl2);
}
控制臺輸出:
first:<BViewController: 0x7f9ccfe0dcd0> - second:<BViewController: 0x7f9ccfe0e5c0>
first ctrl:<BViewController: 0x7f9ccfe0dcd0> - second ctrl:<BViewController: 0x7f9ccfe0e5c0>
2.2 手動構(gòu)建實例
如果你需要介入實例的構(gòu)建過程部宿,你可以使用 block 回調(diào)來配置你的實例對象:
-(void)configure {
[self bindBlock:^id(JSObjectionInjector *context) {
AObject* ob = AObject.new;
ob.bj = [[BObject alloc] initWithName:@"xiaoming"];
return ob;
} toClass:AObject.class];
}
雖然 block 允許你手動構(gòu)建實例抄腔,但是我們通常是需要外部的一些參數(shù)來創(chuàng)建該實例的,該回調(diào)并沒有將外部的參數(shù)傳遞進來理张,不過不用擔(dān)心赫蛇,你可以創(chuàng)建符合 ObjectionProvider
協(xié)議的類,在該類中雾叭,你可以接收到外部傳入的參數(shù)悟耘,通過這些參數(shù)來創(chuàng)建依賴。
@interface AObjectProvider : NSObject <JSObjectionProvider>
@end
@implementation AObjectProvider
-(id)provide:(JSObjectionInjector *)context arguments:(NSArray *)arguments{
AObject* ob = AObject.new;
NSString* name = arguments.firstObject;
ob.bj = [[BObject alloc] initWithName:name];
return ob;
}
@end
-(void)configure {
[self bindProvider:AObjectProvider.new toClass:AObject.class];
}
在合適的時機獲取對象:
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* ob1 = [injector getObject:AObject.class argumentList:@[@"xiaoming"]];
// [injector getObjectWithArgs:AObject.class, @"xiaoming", nil];
NSLog(@"%@ - %@", ob1, ob1.bj.name);
}
控制臺輸出:
<AObject: 0x600000a3e400> - xiaoming
2.3 作用域
在模塊中织狐,類的范圍可以是單例暂幼。 相應(yīng)的,在注入器的上下文中移迫,已注冊的單例可以降級為正常的生命周期旺嬉。
@implementation MyModule
-(void)configure {
[self bindClass:[Singleton class] inScope:JSObjectionScopeNormal];
[self bindClass:[AObject class] inScope:JSObjectionScopeSingleton];
}
@end
這里的
Singleton
單例并非 OC 中真正的單例,而是 Objection 中的JSObjectionScopeSingleton
類型起意,即你無法將自定義的單例類降級鹰服。
我們給 A 對象注冊為 JSObjectionScopeSingleton
類型,然后使用降級揽咕。
@implementation AObject
objection_register_singleton(self)
……
@end
@implementation MyModule
-(void)configure {
[self bindClass:AObject.class inScope:JSObjectionScopeNormal];
}
@end
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* ob1 = [injector getObject:AObject.class];
AObject* ob2 = [injector getObject:AObject.class];
NSLog(@"%@ - %@", ob1, ob2);
}
控制臺輸出:
<AObject: 0x6000027341e0> - <AObject: 0x600002734240>
雖然 A 類注冊了單例類型悲酷,但是我們通過模塊的降級處理吆视,從注入器中獲取的實例對象依舊是不同的禁荸。
2.4 多個模塊
可以通過已創(chuàng)建的注入器加入更多的模塊:
// 啟用默認的注入器
JSObjectionInjector* injector = [JSObjection defaultInjector];
injector = injector ? : [JSObjection createInjector];
// 添加自定義的模塊
injector = [injector withModule:MyModule.new];
// 添加更多的模塊
injector = [injector withModules:MyModule1.new, MyModule2.new, nil];
[JSObjection setDefaultInjector:injector];
-withModule:
方法加入的模塊张足,會將之前相同的綁定覆蓋檩帐,如果你想更換甭定伍玖、作用域等等券膀,就可以使用該方法加入新的綁定钟鸵。
既然能夠加入性昭,那么對應(yīng)的,withoutModuleOfType:
將會移除之前的模塊屠尊。
2.5 +load
關(guān)于注入器的創(chuàng)建旷祸,除了在應(yīng)用啟動時進行配置,我們可以將其放在 +load
中讼昆,該方法會在編譯的過程中被調(diào)用托享,這樣做的好處就是我們不需要在 AppDelegate
中導(dǎo)入各種模塊類,后期修改時浸赫,也不需要手動移除闰围。
@implementation MyModule
+(void)load{
JSObjectionInjector* injector = [JSObjection defaultInjector];
injector = injector ? : [JSObjection createInjector];
injector = [injector withModule:[self.class new]];
[JSObjection setDefaultInjector:injector];
}
-(void)configure {
// 綁定……
}
@end
3.0 總結(jié)
Objection
幫助你實現(xiàn)【依賴注入】,你只需要完成兩件事情既峡,配置依賴關(guān)系和獲取依賴對象羡榴。配置依賴關(guān)系時,你可以使用幾個常用的宏來快速的完成依賴關(guān)系的配置运敢,另外你還可以使用模塊的概念來完成更多的綁定操作校仑,它允許你將某些類或某些協(xié)議接口綁定到某個或某一類的對象上,在配置完成后者冤,你就可以使用關(guān)鍵的 injector
注入器獲取到你所需要的對象肤视。
Objection
像是一種字典容器,通過多種形式將 value
和 key
關(guān)聯(lián)起來涉枫,在完成配置之后邢滑,你只需要關(guān)注你通過何種 key
獲取到需要的 value
即可。Objection
最主要的功能之一就是面向接口編程的實現(xiàn)愿汰,在上面的示例中也進行了演示困后,面向接口編程是一種非常重要的編程思想。
Typhoon
Typhoon 使用 Objective-C 運行時來收集元數(shù)據(jù)并實例化對象衬廷。相比于 Objection 摇予,它有 Swift 版本,并且有著自己的團隊進行維護吗跋。用官網(wǎng)的介紹侧戴,Typhoon 有著以下的優(yōu)勢:
- 不需要宏或XML,使用功能強大的 ObjC 運行時檢測跌宛;
- 支持 IDE 重構(gòu)酗宋,代碼完成和編譯時檢查;
- 提供配置詳細信息的完全模塊化和封裝疆拘;
- 可以使用任何順序聲明依賴項蜕猫;
- 使具有相同基類或協(xié)議的多個配置變得非常容易;
- 支持注入視圖控制器哎迄;
- 支持初始化注入和屬性注入回右,以及生命周期管理隆圆;
- 強大的內(nèi)存管理功能。提供預(yù)配置的對象翔烁,沒有單例的內(nèi)存開銷渺氧;
- 支持循環(huán)依賴;
- 占用空間極低租漂,非常適合 CPU 和 內(nèi)存受限的設(shè)備阶女;
- 功能豐富颊糜,輕量級哩治;
- 歷經(jīng)實戰(zhàn)測試 。
簡單示例
1.1 創(chuàng)建 Assembly
類似于 Objection 的注入器衬鱼,Typhoon 需要 TyphoonAssembly 的子類實例业筏,該實例用于管理、配置依賴的各種實例鸟赫。
@interface MyAssembly : TyphoonAssembly
-(Person*)person;
-(id<Action>)animal;
@end
1.2 配置依賴
-(Person *)person{
return [TyphoonDefinition withClass:[Person class] configuration:^(TyphoonDefinition *definition) {
// 進行配置
// 構(gòu)造器/類方法注入
[definition useInitializer:@selector(initWithName:age:) parameters:^(TyphoonMethod *initializer) {
// 注入相應(yīng)的參數(shù)蒜胖,順序一致
[initializer injectParameterWith:@"xiaoming"];
[initializer injectParameterWith:@(26)];
}];
}];
}
-(id<Action>)animal{
return [TyphoonDefinition withClass:Animal.class];
}
1.3 獲取依賴
MyAssembly* assembly = [[MyAssembly new] activated];
Person* p = assembly.person;
Animal* animal = assembly.animal;
至此,我們完成了 Typhoon 的依賴注入的簡單使用抛蚤,Assembly 的創(chuàng)建 -> 依賴的配置 -> 依賴的獲取台谢。
從使用來看,Typhoon 的思路很簡單岁经,創(chuàng)建一個依賴注入的管理容器朋沮,在該容器中配置各種依賴關(guān)系,接著在需要的地方獲取對應(yīng)的依賴實例缀壤。似乎并沒有亮點之處樊拓,不同之處是用于配置依賴的創(chuàng)建方法進行了統(tǒng)一。
更多的注入方式
2.1 初始化/類方法注入
在上一節(jié)中塘慕,我們使用的就是此種注入方法筋夏。幾個注意點:
- 構(gòu)造器方法可以是實例方法也可以是類方法。
- 可以是無參的創(chuàng)建方法图呢,例如[Person person]条篷。
- 如果沒有給出初始化方法,那么就會使用[[alloc] init]蛤织。
由于 OC 運行時沒有提供參數(shù)類型的信息赴叹,因此 Typhoon 無法提供參數(shù)類型,如果你想要通過類型進行注入瞳筏,那么你需要使用下面的屬性注入的方式稚瘾。
2.2 屬性注入
-(Person *)person{
return [TyphoonDefinition withClass:[Person class] configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(name) with:@"xiaoming"];
[definition injectProperty:@selector(age) with:@(26)];
}];
}
屬性注入可以通過類型完成。另外姚炕,你還可以使用自動注入宏來完成屬性注入摊欠。
2.2.1 自動注入?yún)f(xié)議
為了介紹宏的作用丢烘,我們來演示不用自動宏的情況下的示例:
我們給類 Person
添加一個遵循協(xié)議接口 Action
的 dog 實例。
#import <Typhoon/Typhoon.h>
#import "Action.h"
@interface Person : NSObject
@property (copy, nonatomic) NSString* name;
@property (assign, nonatomic) NSInteger age;
@property (strong, nonatomic) id <Action> dog;
// 構(gòu)造器
-(instancetype)initWithName:(NSString*)name age:(NSInteger)age;
@end
Action
協(xié)議內(nèi)容如下:
#import <UIKit/UIKit.h>
// 行為接口協(xié)議
@protocol Action <NSObject>
@optional
@property (assign, nonatomic) CGFloat height;
-(void)run;
@end
我們的 Assembly
需要手動將 animal
注入到屬性 dog 上:
@implementation MyAssembly
-(Person *)person{
return [TyphoonDefinition withClass:[Person class] configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(name) with:@"xiaoming"];
[definition injectProperty:@selector(age) with:@(26)];
[definition injectProperty:@selector(dog) with:self.animal];
}];
}
-(id<Action>)animal{
return [TyphoonDefinition withClass:Animal.class configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(height) with:@(23.5)];
}];
}
@end
當(dāng)你使用了自動注入宏后些椒,你可以省去手動注入的代碼:
#import <Typhoon/Typhoon.h> // 導(dǎo)入頭文件
@interface Person : NSObject
@property (copy, nonatomic) NSString* name;
@property (assign, nonatomic) NSInteger age;
@property (strong, nonatomic) InjectedProtocol(Action) dog; // 使用自動注入宏
-(instancetype)initWithName:(NSString*)name age:(NSInteger)age;
@end
@implementation MyAssembly
-(Person *)person{
return [TyphoonDefinition withClass:[Person class] configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(name) with:@"xiaoming"];
[definition injectProperty:@selector(age) with:@(26)];
}];
}
-(id<Action>)animal{
return [TyphoonDefinition withClass:Animal.class configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(height) with:@(23.5)];
}];
}
@end
這樣播瞳,MyAssembly
會自動關(guān)聯(lián)遵循 Action
的依賴配置 animal
。
但是免糕,使用該宏需要注意的:
-
Assembly
中必須有且只能有一個符合條件的實例赢乓; - 如果
Person
中需要多個遵循協(xié)議的都會被指定到同一個實例; - 使用了宏之后石窑,不需要寫
id <協(xié)議>
這樣的聲明牌芋。
2.2.2 自動注入類
類似 InjectedProtocol
,InjectedClass
對應(yīng)注入類松逊,它的使用和注意事項和協(xié)議的基本一致躺屁。
2.2.3 When and Why?
在知道自動注入宏的特點之后,什么情況下使用经宏,什么情況下不應(yīng)該使用呢犀暑?
使用
- 當(dāng)你想省事時
- 當(dāng)你想要在類中記錄哪些屬性需要依賴時
不應(yīng)使用
- 當(dāng)你想要避免 Typhoon 侵入你的任何類,只想注入過程出現(xiàn)在 TyphoonAssembly 時
- 當(dāng)你只想集中使用 Typhoon 時
建議
- 對頂層組件(例如視圖控制器)使用自動注入
- 對測試用例使用自動注入
2.3 方法注入
-(Person*)personWithMethodInjection{
return [TyphoonDefinition withClass:Person.class configuration:^(TyphoonDefinition *definition) {
[definition injectMethod:@selector(setName:age:) parameters:^(TyphoonMethod *method) {
[method injectParameterWith:@"xiaoming"];
[method injectParameterWith:@(26)];
}];
}];
}
方法注入和初始化注入非常類似烁兰。
2.4 注入的回調(diào)時機
Typhoon 允許你參與注入的時機耐亏,給 Typhoon 傳入指定的方法,Typhoon 會在注入的前后進行回調(diào)沪斟。
// 注入前執(zhí)行某方法
[definition performBeforeInjections:@selector(beforeInjectionsAction)];
// 注入后執(zhí)行某方法
[definition performAfterInjections:@selector(afterInjectionsAction:) parameters:^(TyphoonMethod *method) {
// 添加參數(shù)
[method injectParameterWith:@"xiaoming"];
}];
如果你不介意被 Typhoon 侵入广辰,你可以導(dǎo)入頭文件 Typhoon.h
,在相應(yīng)的方法中得到注入的時機币喧。
#import Typhoon.h
- (void)typhoonWillInject{
}
- (void)typhoonDidInject{
}
2.5 運行時參數(shù)注入
允許配置依賴實例是帶入?yún)?shù)轨域,像如同一般的方法定義:
-(BViewController*)detailControllerForPerson:(Person*)p{
return [TyphoonDefinition withClass:BViewController.class configuration:^(TyphoonDefinition *definition) {
[definition useInitializer:@selector(initWithPerson:) parameters:^(TyphoonMethod *initializer) {
[initializer injectParameterWith:p];
}];
}];
}
參數(shù)必須是一個對象類型,基本數(shù)據(jù)類型需要進行包裝(NSValue杀餐、NSNumber)干发。
2.6 工廠注入
-(PersonFactory*)personFactory{
return [TyphoonDefinition withClass:PersonFactory.class];
}
-(Person*)student{
return [TyphoonDefinition withFactory:[self personFactory] selector:@selector(personWithTag:) parameters:^(TyphoonMethod *factoryMethod) {
[factoryMethod injectParameterWith:@"student"];
}];
}
2.7 循環(huán)依賴
Typhoon 支持循環(huán)依賴。例如控制器需要視圖史翘,視圖需要控制器作為它的代理枉长。
- (SettingsController *)appSettingsController{
return [TyphoonDefinition withClass:[AppSettingsController class]
configuration:^(TyphoonDefinition* definition){
[definition useInitializer:@selector(initWithSoundManager:settingsView:)
parameters:^(TyphoonMethod* initializer){
[initializer injectParameterWith:[_kernel soundManager]];
[initializer injectParameterWith:[self appSettingsView]];
}];
[definition injectProperty:@selector(title) with:@"Settings"];
}];
}
- (SettingsView *)appSettingsView{
return [TyphoonDefinition withClass:[AppSettingsView class]
configuration:^(TyphoonDefinition* definition){
[definition injectProperty:@selector(delegate) with:
[self appSettingsController]];
}];
2.8 抽象和基類
如果你想要在派生類中共享配置,你可以像下面這樣使用:
-(Person *)person{
return [TyphoonDefinition withClass:[Person class] configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(name) with:@"xiaoming"];
[definition injectProperty:@selector(age) with:@(26)];
}];
}
-(Student *)student{
return [TyphoonDefinition withParent:self.person class:Student.class configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(level) with:@(13)];
}];
}
Student 將從其父級 Person 繼承初始化程序琼讽,屬性注入必峰,方法注入和作用域。 這些都可以被覆蓋钻蹬。父類可以無限地串連在一起吼蚁。
2.9 作用域
和 Objection
一樣,Typhoon
也有作用域的概念。
-(Person *)person{
return [TyphoonDefinition withClass:[Person class] configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(name) with:@"xiaoming"];
[definition injectProperty:@selector(age) with:@(26)];
definition.scope = TyphoonScopeSingleton;
}];
}
在 TyphoonScopeSingleton
類型作用下肝匆,將獲取的相同的對象粒蜈。
3.0 小結(jié)
Typhoon 采用了簡潔的管理依賴的形式,緊緊圍繞著依賴注入的兩種注入方式:方法注入和屬性注入展開旗国,將依賴對象的構(gòu)建枯怖、配置進行了相對地統(tǒng)一。我們只需要繼承自TyphoonAssembly
能曾,就可以是生成一個注入容器度硝。
總結(jié)
Objection 和 Typhoon 都是優(yōu)秀的依賴注入的三方庫。Objection 提供了一個全局的注入器寿冕,是所有依賴對象獲取的入口蕊程。針對單獨的類中的依賴對象,Objection 提供了便捷的宏來添加依賴對象的創(chuàng)建(objection_requires_sel
和objection_initializer_sel
分別對應(yīng)屬性注入和構(gòu)造器注入),你還可以通過模塊的概念將功能劃分統(tǒng)一配置管理蚂斤,綁定的類型豐富多樣存捺,但是 Objection 的侵入性較強。Typhoon 侵入性較低曙蒸,使用的形式比較統(tǒng)一規(guī)范,它的注入容器都需要繼承自 TyphoonAssembly岗钩,沒有指定的統(tǒng)一入口纽窟。