天天看點

Objection, 一個輕量級的Objective-C依賴注入架構

簡介

  • 項目首頁:https://github.com/atomicobject/objection
  • 執行個體下載下傳: https://github.com/ios122/ios122

Objection 是一個輕量級的Objective-C依賴注入架構,可同時用于MacOS X 或者iOS.對于那些使用過Guice(一個Java依賴注入架構)的開發者,會感覺Objection 似曾相識.Objection用來以一種相對容易接受的方式來使你盡可能地不需要管理一個龐大的XML容器或者手動建立對象.

特點

  • "Annotation" 基于依賴注入.
  • 無縫支援自定義內建和依賴擴充.
  • 自定義綁定時類的建立方式.
  • 元類綁定.
  • 協定綁定.
  • 執行個體對象綁定.
  • 别名綁定.
  • 懶加載.
  • 及早計算的單例.
  • 自定義初始化方式.
  • 自定義參數和預設值.

系統要求

  • MacOS X 10.8 +
  • iOS 7.0 +

使用CocoaPods安裝

注意podfile中需要指明Objection的版本号,否則無法安裝成功.

pod 'Objection', '1.6.1' # 依賴注入.           

複制

然後在需要的地方導入即可頭檔案即可:

#import <Objection/Objection.h>           

複制

使用 Objection

基礎用法

一個類可以使用宏 objection_register(可選)或 objection_register_singleton 注冊到 objection. objection_requires 宏用來聲明objection應該為此類的所有執行個體提供的依賴.objection_requires在類的繼承體系中也可以安全使用.

  • objection_requires 宏,僅在從從注射器中擷取類的執行個體時,才有意義.從注射器中擷取類執行個體的方法,下面會具體讨論.
  • objection_requires 宏聲明依賴後,使用注射器來擷取此類執行個體時,會自動建立依賴類的執行個體,并指派給響應的屬性.
  • 如果使用 objection_register_singleton 宏注冊一個類,并堅持使用注射器來擷取此類的執行個體,那此類就不用自己實作單例機制了.

示例.

@class Engine, Brakes;

@interface Car : NSObject

// 将會通過依賴注入指派.
@property(nonatomic, strong) Engine *engine;
// 将會通過依賴注入指派.
@property(nonatomic, strong) Brakes *brakes;
@property(nonatomic) BOOL awake;

@implementation Car
objection_requires(@"engine", @"brakes")
@synthesize engine, brakes, awake;
@end           

複制

使用選擇器定義依賴.

你也可以使用選擇器來定義依賴.如果給定的選擇器在目前作用域看不見或無法找到,編譯器會産生一個警告.

示例

@implementation Car
objection_requires_sel(@selector(engine), @selector(brakes))
@synthesize engine, brakes, awake;
@end           

複制

從Objection中擷取對象.

可以建立一個注射器,然後從這個注射器中擷取指定類或協定的一個執行個體.注射器各自管理自己的對象上下文.這意味着:Objection中的單例指的是一個注射器中隻存在一個某個類的執行個體,并不一定是真正意義上的單例(即那種應用程式全局唯一的類的執行個體對象).

- (void)someMethod {
  JSObjectionInjector *injector = [JSObjection createInjector];
  id car = [injector getObject:[Car class]];
}           

複制

一個給Objection設定一個預設的注射器.這個設定器可以在你的應用或庫内,全局可用.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
  JSObjectionInjector *injector = [JSObjection createInjector];
  [JSObjection setDefaultInjector:injector];
}

- (void)viewDidLoad {
  id myModel = [[JSObjection defaultInjector] getObject:[MyModel class]];
}           

複制

依賴注入

有可能類的執行個體對象并不是通過注射器建立的,此時如果不做特殊處理,依賴不會被正确處理,相關屬性可能為nil.但是如果對于使用 objection_requires宏指定依賴的情況,你可以通過injectDependencies:方法來實作:即使不使用注射器也能保證依賴被滿足.

@implementation JSTableModel
objection_requires(@"RESTClient")
- (void)awakeFromNib {
  [[JSObjection defaultInjector] injectDependencies:self];
}
@end           

複制

下标操作

Objection 已經支援使用下标操作來從注射器上下文來擷取對象.

- (void)someMethod {
  JSObjectionInjector *injector = [JSObjection createInjector];
  id car = injector[[Car class]];
}           

複制

從Objection中建立的對象.

如果一個對象需要知道它使合适被objection完全初始化的,可以實作方法awakeFromObjection .注意:對象被Objection完全初始化時會調用awakeFromObjection方法,你在這裡可以加入自定義的一些操作;而awake隻是一個例子中的自定義标記屬性而已,并不是Objection的一部分.

示例

@implementation Car
//...
objection_register_singleton(Car)
  - (void)awakeFromObjection {
    self.awake = YES;
  }
@end             

複制

對象工廠

一個對象可以通過對象工廠來從注射器上下文來擷取對象.

自定義JSObjectFactory屬性,需要使用 objection_requires 宏來指明依賴,如 objection_requires(@"objectFactory") .這樣當從注射器中擷取這個類的執行個體時,會自動擷取與此注射器相關的JSObjectFactory對象工廠執行個體.

示例

@interface RequestDispatcher
@property(nonatomic, strong) JSObjectFactory *objectFactory
@end

@implementation RequestDispatcher
objection_requires(@"objectFactory") 

- (void)dispatch:(NSDictionary *)params
{
  Request *request = [self.objectFactory getObject:[Request class]];
  request.params = params;
  [request send];
}
@end           

複制

子產品

一個子產品就是一組綁定資訊.這些綁定資訊用來給注射器增加額外的配置資訊.它在整合外部依賴和綁定協定到類或執行個體時特别有用.

執行個體和協定的綁定

  • 綁定一個協定或類到該類型指定的某個執行個體.
  • 綁定一個已經注冊到Objection的類到一個協定.

示例

@interface MyAppModule : JSObjectionModule {
  
}
@end

@implementation MyAppModule
- (void)configure {
  [self bind:[UIApplication sharedApplication] toClass:[UIApplication class]];
  [self bind:[UIApplication sharedApplication].delegate toProtocol:@protocol(UIApplicationDelegate)];
  [self bindClass:[MyAPIService class] toProtocol:@protocol(APIService)];
}

@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
  JSObjectionInjector *injector = [JSObjection createInjector:[[MyAppModule alloc] init]];
  [JSObjection setDefaultInjector:injector];
}           

複制

元類的綁定

有時候,我們僅僅是想使用依賴的某個類的類方法.Objection可以通過協定顯示地支援元類的綁定.這樣就不用再建立一個包裝類來傳遞類方法.要注意的是,它需要定義一個協定來讓Objection知道如何綁定元類到注射器的對象上下文.

示例

@protocol ExternalUtility
  - (void)doSomething;  //!< 注意此處,确實是`-`減号.通常是不支援讓元類直接支援協定的.此處是以類本身作為對象,來取執行協定,而不是使用該類的某一個執行個體.
@end

@interface ExternalUtility
  + (void)doSomething;
@end

@implementation ExternalUtility
  + (void)doSomething {...}
@end

// Module Configuration
- (void)configure {
  [self bindMetaClass:[ExternalUtility class] toProtocol:@protocol(ExternalUtility)];    
}

@interface SomeClass
{
  ...
}
//  使用 'assign' 是因為一個元類不受通常的 retain/release聲明周期限制.
// 它将會一直存在,直到應用程式終止(類初始化 -> 應用終止),不管運作時有多少指向它的對象引用.
// 
@property (nonatomic, assign) id<ExternalUtility> externalUtility
@end           

複制

提供者

偶爾你可能想要在Objection内部手動構造一個對象.提供者允許你使用自定義的機制來建立某個類型的對象.你可以建立一個 遵守 ObjectionProvider 協定的對象,或者你可以使用一個 block 來建立對象.

如果使用了對像提供者,則原類中的

-awakeFromObjection

方法在此類的執行個體通過注射器建立完成後,不會再被調用.

示例

@interface CarProvider : NSObject <JSObjectionProvider>
@end

@implementation CarProvider
- (id)provide:(JSObjectionInjector *)context arguments:(NSArray *)arguments {
  // 手動建立對象的代碼
  return car;
}
@end

@implementation MyAppModule
- (void)configure {
    [self bindProvider:[[CarProvider alloc] init] toClass:[Car class]];
    [self bindBlock:^(JSObjectionInjector *context) {
      // 手動建立對象.
      return car;          
    } toClass:[Car class]];
}
@end           

複制

作用域

一個類被用作子產品作用域内的單例.相反,一個已經注冊的單例在也可以被降級為注射器上下文中一個普通聲明周期的執行個體對象.

也就是說,你有兩種方式來指定類執行個體在注射器上下文是單例對象還是普通對象.一種是在類實作中使用 objection_register_singleton 宏,一種是在子產品配置方法中指定作用域為JSObjectionScopeSingleton.

示例

@implementation MyAppModule
- (void)configure {
    [self bindClass:[Singleton class] inScope:JSObjectionScopeNormal];
    [self bindClass:[Car class] inScope:JSObjectionScopeSingleton];
}
@end           

複制

别名綁定

同一個類或協定的依賴可以使用 objection_requires_names 宏标記,這個宏使用屬性别名字典作為參數.

示例

@interface ShinyCar : NSObject
@property (nonatomic, strong) Headlight *leftHeadlight;
@property (nonatomic, strong) Headlight *rightHeadlight;
@end

@implementation ShinyCar
objection_register(ShinyCar)
objection_requires_names((@{@"LeftHeadlight":@"leftHeadlight", @"RightHeadlight":@"rightHeadlight"}))
@synthesize leftHeadlight, rightHeadlight;
@end

@implementation NamedModule

- (void)configure
{
    [self bind:[[Headlight alloc]init] toClass:[Headlight class] named:@"RightHeadlight"];
    [self bindClass:[HIDHeadlight class] toClass:[Headlight class] named:@"LeftHeadlight"];

}
@end           

複制

及早初始化的單例

你可以将已經注冊的單例用作及早初始化的單例.及早初始化的單例,在注射器建立時建立,而不再是懶加載.

注意:如果将一個未注冊為單例的類設定為及早初始化的單例,會引起崩潰.

Example

@implementation MyAppModule
- (void)configure {
  [self registerEagerSingleton:[Car class]];
}

@end           

複制

從一個已經存在的注射器派生一個新的注射器

一個新的注射器可以使用 withModule: 方法從一個已經存在的注射器建立.這個新的注射器将會和派生它的注射器擁有同樣的綁定資訊.

與之相反,如果使用 withoutModuleOfType:,新注射器就不會包含被标記為不包含的子產品.

示例

injector = [otherInjector withModule:[[Level18Module alloc] init]] 
                          withoutModuleOfType:[Level17Module class]];           

複制

初始化

預設地,Objection 使用預設的初始化方法

init

建立對象.如果你想使用其他的初始化方法來初始化對象,可以借助

objection_initializer

宏.這個宏支援傳遞預設參數(暫時不支援标量參數,即基本類型參數,如數字).

預設參數示例

@implementation ViewController
objection_initializer(initWithNibName:bundle:, @"ViewController")
@end           

複制

自定義參數示例

@implementation ConfigurableCar
objection_requires(@"engine", @"brakes")
objection_initializer(initWithMake:model:)

@synthesize make;
@synthesize model;

- (instancetype)initWithMake:(NSString *)make model:(NSString *)model {
  ...
}
@end

- (void)buildCar {
  ConfigurableCar *car = [self.objectFactory getObjectWithArgs:[ConfigurableCar class], @"VW", @"Passat", nil];
  NSLog(@"Make: %@ Model: %@", car.make, car.model);
}           

複制

類方法初始化

@implementation Truck
objection_requires(@"engine", @"brakes")
objection_initializer(truckWithMake:model:)
+ (instancetype)truckWithMake:(NSString *) make model: (NSString *)model {
  ...
}
@end           

複制

專用初始化方法

@implementation ConfigurableCar
- (instancetype) initWithModel:(NSString *)model {
    //....
}
@end

- (void)buildCar {
  ConfigurableCar *car = [self.objectFactory getObject:[ConfigurableCar class], 
                                           initializer: @selector(initWithModel:) 
                                           withArgumentList:@[@"Passat"]];
}           

複制

測試

如果你正在使用 Kiwi 來進行應用的測試, 請檢出MSSpec.它提供了一種把虛拟資料注入到你的測試标準中的便利方式.