iOS 依賴注入:Objection 和 Typhoon

什么是依賴

對象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 依賴注入的兩大框架 ObjectionTyphoon 的使用跟畅,幫助大家更好的理解依賴注入以及面向接口編程的思想咽筋。


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_requiresobjection_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_initializerobjection_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 的使用類比為字典容器的使用,通過 keyvalue 進行綁定砍的,然后通過 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 像是一種字典容器,通過多種形式將 valuekey 關(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é)中塘慕,我們使用的就是此種注入方法筋夏。幾個注意點:

  1. 構(gòu)造器方法可以是實例方法也可以是類方法。
  2. 可以是無參的創(chuàng)建方法图呢,例如[Person person]条篷。
  3. 如果沒有給出初始化方法,那么就會使用[[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 自動注入類

類似 InjectedProtocolInjectedClass 對應(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_selobjection_initializer_sel分別對應(yīng)屬性注入和構(gòu)造器注入),你還可以通過模塊的概念將功能劃分統(tǒng)一配置管理蚂斤,綁定的類型豐富多樣存捺,但是 Objection 的侵入性較強。Typhoon 侵入性較低曙蒸,使用的形式比較統(tǒng)一規(guī)范,它的注入容器都需要繼承自 TyphoonAssembly岗钩,沒有指定的統(tǒng)一入口纽窟。


相關(guān)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兼吓,隨后出現(xiàn)的幾起案子臂港,更是在濱河造成了極大的恐慌,老刑警劉巖视搏,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件审孽,死亡現(xiàn)場離奇詭異,居然都是意外死亡浑娜,警方通過查閱死者的電腦和手機佑力,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筋遭,“玉大人打颤,你說我怎么就攤上這事±焯希” “怎么了编饺?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長响驴。 經(jīng)常有香客問我透且,道長,這世上最難降的妖魔是什么豁鲤? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任秽誊,我火速辦了婚禮罕邀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘养距。我一直安慰自己诉探,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布棍厌。 她就那樣靜靜地躺著肾胯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耘纱。 梳的紋絲不亂的頭發(fā)上敬肚,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音束析,去河邊找鬼艳馒。 笑死,一個胖子當(dāng)著我的面吹牛员寇,可吹牛的內(nèi)容都是我干的弄慰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蝶锋,長吁一口氣:“原來是場噩夢啊……” “哼陆爽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扳缕,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤慌闭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后躯舔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驴剔,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年粥庄,在試婚紗的時候發(fā)現(xiàn)自己被綠了丧失。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡飒赃,死狀恐怖利花,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情载佳,我是刑警寧澤炒事,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蔫慧,受9級特大地震影響挠乳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一睡扬、第九天 我趴在偏房一處隱蔽的房頂上張望盟蚣。 院中可真熱鬧,春花似錦卖怜、人聲如沸屎开。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奄抽。三九已至,卻和暖如春甩鳄,著一層夾襖步出監(jiān)牢的瞬間逞度,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工妙啃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留档泽,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓揖赴,卻偏偏與公主長得像馆匿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子储笑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹 對...
    cosWriter閱讀 11,089評論 1 32
  • 依賴注入(Dependency Injection) 依賴注入最大的特點就是:幫助我們開發(fā)出松散耦合(loose ...
    小李龍彪閱讀 2,385評論 1 0
  • 一甜熔、簡介 Objection是一個iOS中輕量級的對DI及IoC的實現(xiàn),不知道DI及IoC的請移步至iOS組件通信...
    fou7閱讀 5,590評論 2 15
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔突倍,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,732評論 2 9
  • 依賴注入(Dependency Injection) 今天我們討論的內(nèi)容核心是面向接口編程盆昙,我決定還是要從依賴注入...
    zhiyi閱讀 14,038評論 8 79