簡介
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.它提供了一種把虛擬數據注入到你的測試標准中的便利方式.