背景
最近接觸了一段時(shí)間的SpringMVC吼驶,對(duì)其控制反轉(zhuǎn)(IoC)和依賴注入(DI)印象深刻污抬,此后便一直在思考如何使用OC語言較好的實(shí)現(xiàn)這兩個(gè)功能检痰。Java語言自帶的注解特性為IoC和DI帶來了極大的方便敦第,要在OC上較好的實(shí)現(xiàn)這兩個(gè)功能,需要一些小小的技巧理卑。
控制反轉(zhuǎn)和依賴注入
控制反轉(zhuǎn)
簡(jiǎn)單來說,將一個(gè)類對(duì)象的創(chuàng)建由手動(dòng)new方式改為從IOC容器內(nèi)獲取蔽氨,就是一種控制反轉(zhuǎn)藐唠,例如我們現(xiàn)在要?jiǎng)?chuàng)建一個(gè)ClassA類,則常規(guī)方法為
ClassA *a = [ClassA new];
如果使用控制反轉(zhuǎn)孵滞,則從容器內(nèi)獲取ClassA對(duì)象中捆,對(duì)象由容器負(fù)責(zé)創(chuàng)建。
ApplicationContext *context = [ApplicationContext sharedContext];
ClassA *a = [context getInstanceByClassName:@"ClassA"];
依賴注入
所謂依賴注入坊饶,是指一個(gè)類對(duì)象不應(yīng)負(fù)責(zé)去查找其依賴屬性泄伪,而是交由容器去處理。例如ClassA類有一個(gè)ClassB類對(duì)象匿级,如下所示蟋滴。
@interface ClassA : NSObject
@property(nonatomic, strong) ClassB *b;
@end
常規(guī)情況下,要將ClassB的對(duì)象創(chuàng)建后傳遞給ClassA痘绎,如下所示津函。
ClassA *a = [ClassA new];
ClassB *b = [ClassB new];
a.b = b;
如果交由容器處理,則容器會(huì)自動(dòng)創(chuàng)建ClassA孤页、ClassB尔苦,并且根據(jù)ClassA的依賴屬性類型ClassB自動(dòng)的將ClassB的實(shí)例注入到ClassA對(duì)象中。
優(yōu)點(diǎn)
使用控制反轉(zhuǎn)和依賴注入將對(duì)象的創(chuàng)建與依賴對(duì)象的注入交由IoC容器來完成行施,這樣做不僅能夠降低代碼的耦合度允坚,更可以減少代碼量。
類與屬性的修飾
這兩個(gè)功能都是在運(yùn)行時(shí)通過反射來實(shí)現(xiàn)的蛾号,具體的容器技術(shù)細(xì)節(jié)在下文討論稠项,這里主要討論的是如何修飾交由容器創(chuàng)建的對(duì)象以及依賴注入的屬性。
Java中的注解
在Java中鲜结,那些要交由Spring容器創(chuàng)建的類通過注解來修飾展运,例如JavaWeb中的Controller和Service分別由@Controller和@Service修飾活逆,如下所示。
// 控制器
@Controller
public class SomeController {...}
// 服務(wù)
@Service
public class SomeService {...}
而控制器要通過服務(wù)來處理業(yè)務(wù)邏輯拗胜,因此Controller依賴了Service蔗候,通過@Autowired修飾這一屬性,即可完成自動(dòng)注入挤土,如下所示琴庵。
@Controller
public class AdministratorController {
@Autowired
private SomeService serv;
...
}
通過@Autowired注解,IoC容器會(huì)根據(jù)屬性類型(SomeService)找到依賴對(duì)象的實(shí)例仰美,并且注入到Controller的serv屬性迷殿。這一過程中,Controller咖杂、Service以及注入均不需要寫額外的代碼庆寺,只需要通過注解修飾即可。
OC中的實(shí)現(xiàn)
由于OC沒有注解特性诉字,因此要標(biāo)記類和屬性就需要其他的方法懦尝,我經(jīng)過思考發(fā)現(xiàn)了一種較好的方式,那就是通過協(xié)議來標(biāo)記類壤圃,通過額外的屬性來標(biāo)記屬性陵霉。
- 為了模仿@Controller、@Service等修飾類的注解伍绳,我們使用IOCComponents協(xié)議來標(biāo)記類踊挠,來表示這些類交由容器處理。
@interface SGService : NSObject <IOCComponents>
...
@end
- 為了模仿@Autowired實(shí)現(xiàn)的按類型注入冲杀,我們?cè)谝⑷氲膶傩郧岸嗉右粭l屬性效床,該條屬性的類型為TypeAnnotation,我們稱之為注解屬性权谁,這個(gè)類通過@Class聲明而并不存在剩檀,只是為了標(biāo)記,在反射時(shí)得到的所有屬性都是按照順序排列的旺芽,因此每一條注解屬性后的屬性都是需要進(jìn)行依賴注入的沪猴,示例如下。
@interface SGService : NSObject <IOCComponents>
@property (nonatomic, weak, readonly) TypeAnnotation *autowired_0;
@property (nonatomic, strong) SGMapper *mapper;
@end
通過上文這種方式采章,在反射時(shí)autowired_0和mapper是相鄰的运嗜,因此可以判斷出mapper需要注入,只需要反射出該property的信息共缕,并且從容器中查找,并注入即可士复,為了方便定義這樣的注解屬性图谷,我們使用一個(gè)宏如下所示翩活。
#define Autowired(num) @property (nonatomic, weak, readonly) TypeAnnotation *autowired_##num;
那么上面的代碼可以進(jìn)行如下的簡(jiǎn)化。
@interface SGService : NSObject <IOCComponents>
Autowired(0)
@property (nonatomic, strong) SGMapper *mapper;
@end
這里的數(shù)字是為了多個(gè)注解造成屬性的重復(fù)定義便贵,可以從0開始編號(hào)菠镇。
OC的一個(gè)實(shí)現(xiàn)示例
這里假設(shè)IoC容器已經(jīng)完成,主要演示流程承璃,容器的技術(shù)細(xì)節(jié)在下文討論利耍。
有一個(gè)Service類和一個(gè)Mapper類,Service依賴了Mapper盔粹,具體代碼如下隘梨。
類的定義與修飾
- IOCComponents用于標(biāo)識(shí)該類由容器負(fù)責(zé)創(chuàng)建
- Autowired(x)表示該屬性按類型進(jìn)行依賴注入
@interface SGService : NSObject <IOCComponents>
Autowired(0)
@property (nonatomic, strong) SGMapper *mapper;
@end
@interface SGMapper : NSObject <IOCComponents>
@end
掃描配置
類似于Spring的applicationContext.xml,這里通過ApplicationContext.plist來配置要掃描的類的特征舷嗡,目前提供了前綴和類列表轴猎,對(duì)于有公共前綴的類可以配置一個(gè)前綴來實(shí)現(xiàn)掃描,對(duì)于沒有前綴的類进萄,單獨(dú)配置到類列表中捻脖,如下圖所示。
驗(yàn)證與使用
經(jīng)過上面的配置中鼠,IoC容器便可完成Service與Mapper的創(chuàng)建可婶,以及將Mapper注入到Service,驗(yàn)證如下援雇。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
SGApplicationContext *context = [SGApplicationContext sharedContext];
SGService *serv = [context getInstanceByClassName:@"SGService"];
printf("%s -> %s\n",serv.description.UTF8String, serv.mapper.description.UTF8String);
return YES;
}
打印如下
<SGService: 0x7ff34bc749f0> -> <SGMapper: 0x7ff34bc74920>
IoC容器的實(shí)現(xiàn)
IoC容器的核心功能是對(duì)象創(chuàng)建矛渴、依賴查找和依賴注入,這些功能都需要借助運(yùn)行時(shí)的反射實(shí)現(xiàn)熊杨,下面將按照容器初始化的過程來逐一介紹用到的OC運(yùn)行時(shí)函數(shù)曙旭,這些函數(shù)均可以在包含objc/runtime.h
后使用。
1.IoC容器
容器通過SGApplicationContext單例來管理需要容器創(chuàng)建的類對(duì)象晶府,通過一個(gè)Dictionary類型的屬性instanceMap存儲(chǔ)對(duì)象是否已經(jīng)創(chuàng)建過桂躏,容器中的對(duì)象目前還只是單例的,邏輯很簡(jiǎn)單川陆,每次根據(jù)類型取出對(duì)象時(shí)剂习,先檢查instanceMap該對(duì)象是否已經(jīng)創(chuàng)建過,如果創(chuàng)建過則直接返回较沪,否則創(chuàng)建后返回鳞绕,這里會(huì)通過運(yùn)行時(shí)函數(shù)objc_getClass
檢查該類是否已經(jīng)裝載,具體實(shí)現(xiàn)如下尸曼。
- (id)getInstanceByClassName:(NSString *)className {
if (self.instanceMap[className] == nil) {
Class clazz = NSClassFromString(className);
// 檢查類是否已經(jīng)裝載们何,防止未定義的類實(shí)例化時(shí)出錯(cuò)
if (!objc_getClass(className.UTF8String)) {
return nil;
}
id instance = [clazz new];
self.instanceMap[className] = instance;
}
return self.instanceMap[className];
}
2.掃描類列表
通過函數(shù)objc_getClassList
可以獲取類列表,該函數(shù)的具體信息如下控轿。
int objc_getClassList(Class *buffer, int bufferCount);
buffer是所有類的數(shù)組冤竹,bufferCount為數(shù)組大小拂封,返回值也為數(shù)組大小,由于第一次調(diào)用時(shí)并不知道bufferCount鹦蠕,因此可以兩次調(diào)用冒签,第一次拿到bufferCount,第二次再初始化類列表數(shù)組钟病,具體代碼如下萧恕,詳細(xì)請(qǐng)見注釋。
- (void)scanClasses {
int classCount = objc_getClassList(NULL, 0);
Class *classList = (Class *)malloc(classCount * sizeof(Class));
classCount = objc_getClassList(classList, classCount);
// 用于存放需要IoC容器處理的類的OC數(shù)組
NSMutableArray *temp = @[].mutableCopy;
// 獲得IOCComponents協(xié)議肠阱,用于判斷標(biāo)記
Protocol *protocol = objc_getProtocol("IOCComponents");
for (int i = 0; i < classCount; i++) {
Class clazz = classList[i];
NSString *className = NSStringFromClass(clazz);
// 第一個(gè)判斷條件對(duì)應(yīng)于ApplicationContext.plist中的掃描類特征配置票唆,只有符合條件的類才能被添加
// 第二個(gè)條件檢查IOCComponents標(biāo)記,有標(biāo)記的類才被IoC容器處理
if ([self isValidIOCClassNamed:className] && [clazz conformsToProtocol:protocol]) {
[temp addObject:className];
}
}
// 將IoC需要處理的類存儲(chǔ)起來
self.DIClasses = temp;
// 由于類列表是malloc創(chuàng)建的辖所,需要手動(dòng)釋放
free(classList);
// 根據(jù)注解屬性處理依賴注入
[self scanAnnotation];
}
3.掃描類屬性與屬性注入
要掃描一個(gè)類的所有私有和公有屬性惰说,使用下面的函數(shù)。
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
該方法可以列出所有屬性缘回,先私有后公有吆视,按照定義的順序從上到下列出,其返回值是一個(gè)objc_property_t結(jié)構(gòu)體數(shù)組酥宴,從objc_property_t結(jié)構(gòu)體中可以得到屬性的各種信息啦吧,對(duì)于名稱可以通過property_getName
函數(shù)直接獲得,而其他信息需要先通過property_getAttributes
方法獲得描述屬性的字符串拙寡,再進(jìn)行分割授滓。通過這樣的步驟,即可獲得所有注解屬性的位置肆糕,并由此得到所有需要注入的屬性般堆,根據(jù)這些屬性的類型進(jìn)行注入即可。
要完成注入诚啃,需要先得到代表屬性的Ivar淮摔,然后對(duì)實(shí)例進(jìn)行注入,得到Ivar和注入屬性的函數(shù)如下始赎。
// 注意得到Ivar時(shí)的屬性名為實(shí)例變量名而不是property名和橙,例如@property定義的xx,則這里應(yīng)該寫作_xx
Ivar class_getInstanceVariable(Class cls, const char *name);
// 將value注入到obj實(shí)例的 ivar實(shí)例變量上
void object_setIvar(id obj, Ivar ivar, id value);
具體代碼如下造垛,詳細(xì)請(qǐng)看注釋魔招。
- (void)scanAnnotation {
// 對(duì)scanClasses中得到的需要IoC容器處理的類進(jìn)行遍歷
for (NSUInteger i = 0; i < self.DIClasses.count; i++) {
NSString *className = self.DIClasses[i];
Class class = NSClassFromString(className);
unsigned int outCount;
// 反射出所有屬性
objc_property_t *props = class_copyPropertyList(class, &outCount);
// 保存所有注解屬性,注解屬性包含了位置索引(index)五辽、名稱(name)和類型(type)办斑,通過一個(gè)模型類SGAnnotation來存儲(chǔ)
NSMutableArray *annotations = @[].mutableCopy;
// 保存所有的屬性信息,每個(gè)屬性包含了名稱(name)和類型(type)杆逗,通過一個(gè)模型類SGProperty來存儲(chǔ)
NSMutableArray *properties = @[].mutableCopy;
// 遍歷所有屬性
for (NSUInteger i = 0; i < outCount; i++) {
objc_property_t prop = props[i];
NSString *propName = [[NSString alloc] initWithCString:property_getName(prop) encoding:NSUTF8StringEncoding];
// 這一段代碼用于從描述屬性的字符串中獲取到類型乡翅,用到了正則和字串處理
NSString *propAttrs = [[NSString alloc] initWithCString:property_getAttributes(prop) encoding:NSUTF8StringEncoding];
NSRange range = [propAttrs rangeOfString:@"@\".*\"" options:NSRegularExpressionSearch];
if (range.location != NSNotFound) {
range.location += 2;
range.length -= 3;
NSString *typeName = [propAttrs substringWithRange:range];
// 如果當(dāng)前屬性為注解屬性吁讨,則記錄進(jìn)annotaions
if ([typeName isEqualToString:@"TypeAnnotation"]) {
SGAnnotation *anno = [SGAnnotation new];
anno.index = i;
anno.name = propName;
anno.type = typeName;
[annotations addObject:anno];
}
// 記錄每一條屬性
SGProperty *sp = [SGProperty new];
sp.name = propName;
sp.type = typeName;
[properties addObject:sp];
}
} // scan class properties end
// 從容器中得到類的實(shí)例
id diInstance = [self getInstanceByClassName:className];
// 遍歷注解,得到所有被修飾的屬性
for (NSUInteger i = 0; i < annotions.count; i++){
SGAnnotation *annotation = annotations[i];
SGProperty *prop = properties[annotation.index + 1];
NSString *typeName = prop.type;
NSString *varName = prop.name;
// 得到依賴對(duì)象峦朗,并注入到注解修飾的屬性
id destInstance = [self getInstanceByClassName:typeName];
Ivar ivar = class_getInstanceVariable([diInstance class], [NSString stringWithFormat:@"_%@",varName].UTF8String);
object_setIvar(diInstance, ivar, destInstance);
}
} // scan classes end
}
總結(jié)
通過這樣的處理,完成了OC語言對(duì)IoC和DI的基本實(shí)現(xiàn)排龄,這只是一次探索波势,還有很多地方需要優(yōu)化和完善。