依賴注入(Dependency Injection)這個詞,源于java,但在Cocoa框架中也是十分常見的。
舉例來說:
UIView的初始化方法initWithFrame
- (id)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
這里的frame傳入值,就是所謂的依賴(Dependency),這個View實例化是根據(jù)frame注入實現(xiàn)的。
但這種用法有很大的局限性
- 我們不知道究竟依賴注入的屬性有哪些
- 不可能無限加長方法長度來滿足更多的依賴屬性
所以我們準備采用字典容器對NSObject類進行依賴注入擴展。
給NSObject類添加一個Category
@interface NSObject (XXXDependencyInjection)
- (nullable id)initWithParams:(nonnull NSDictionary *)params;
- (void)injection:(nonnull NSDictionary*)params;
@end
實現(xiàn)注入方法
- (id)initWithParams:(NSDictionary *)params
{
self = [self init];
if (self) {
[self injection:params];
}
return self;
}
- (void)injection:(NSDictionary*)params
{
[params.allKeys enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[[obj substringToIndex:1] uppercaseString],[obj substringFromIndex:1]]);
id value = [params objectForKey:obj];
if ([self respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector withObject:value];
#pragma clang diagnostic pop
}
else
{
@try {
[self setValue:value forKeyPath:obj];
}
@catch (NSException *exception) {
NSLog(@"%@",exception);
[exception raise];
}
@finally {
}
}
}];
}
解釋
我們將需要注入的屬性,封裝到一個字典里,例如:
UIViewController* controller = [[UIViewController alloc] initWithParams:@{
@"title":@"測試",
@"view.backgroundColor":[UIColor whiteColor]
}];
我們給這個VC注入了兩個屬性,一個是其title,一個是其View的backgroundColor屬性。
字典傳入以后,我們讀區(qū)params.allKeys進行遍歷,拼裝set+參數(shù)名的selector,這里用的是NSSelectorFromString方法:
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[[obj substringToIndex:1] uppercaseString],[obj substringFromIndex:1]]);
然后我們判斷實例是否可以響應這個set方法,如果可以,則給其賦值。
if ([self respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector withObject:value];
#pragma clang diagnostic pop
}
這里的三行clang宏是為了消除編譯器的內(nèi)存泄漏警告,這里因為我們進行了驗證,所以不會出現(xiàn)leak。
KVC實現(xiàn)跨實例賦值
我們注意到上例中還有一句給VC的View改變背景顏色
@"view.backgroundColor":[UIColor whiteColor]
這里就用到了KVC的點語法特性,在我們判斷到實例不能響應if ([self respondsToSelector:selector])的時候,通過點語法,進行賦值
@try {
[self setValue:value forKeyPath:obj];
}
@catch (NSException *exception) {
NSLog(@"%@",exception);
[exception raise];
}
@finally {
}
這里添加了異常捕獲,因為點語法對屬性名稱拼寫要求是全匹配,否則拋異常,所以要注意。
優(yōu)缺點
這樣改造過的init方法,優(yōu)點非常明顯,就是綁定更加集中便捷,如果使用的是storyboard則可以輕松實現(xiàn)前后端分離。
目前的缺點也很明顯,不能告訴開發(fā)者哪些屬性是必需依賴,另外還不能支持非對象屬性的賦值(已經(jīng)可以,改為setValueforkeyPath方法賦值),希望拋磚引玉,大家來改進這段代碼。