背景
最近接觸了一段時(shí)間的SpringMVC,對(duì)其控制反轉(zhuǎn)(IoC)和依賴(lài)注入(DI)印象深刻,此后便一直在思考如何使用OC語(yǔ)言較好的實(shí)現(xiàn)這兩個(gè)功能。Java語(yǔ)言自帶的注解特性為IoC和DI帶來(lái)了極大的方便,要在OC上較好的實(shí)現(xiàn)這兩個(gè)功能,需要一些小小的技巧。
控制反轉(zhuǎn)和依賴(lài)注入
控制反轉(zhuǎn)
簡(jiǎn)單來(lái)說(shuō),將一個(gè)類(lèi)對(duì)象的創(chuàng)建由手動(dòng)new方式改為從IOC容器內(nèi)獲取,就是一種控制反轉(zhuǎn),例如我們現(xiàn)在要?jiǎng)?chuàng)建一個(gè)ClassA類(lèi),則常規(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"];
依賴(lài)注入
所謂依賴(lài)注入,是指一個(gè)類(lèi)對(duì)象不應(yīng)負(fù)責(zé)去查找其依賴(lài)屬性,而是交由容器去處理。例如ClassA類(lèi)有一個(gè)ClassB類(lèi)對(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的依賴(lài)屬性類(lèi)型ClassB自動(dòng)的將ClassB的實(shí)例注入到ClassA對(duì)象中。
優(yōu)點(diǎn)
使用控制反轉(zhuǎn)和依賴(lài)注入將對(duì)象的創(chuàng)建與依賴(lài)對(duì)象的注入交由IoC容器來(lái)完成,這樣做不僅能夠降低代碼的耦合度,更可以減少代碼量。
類(lèi)與屬性的修飾
這兩個(gè)功能都是在運(yùn)行時(shí)通過(guò)反射來(lái)實(shí)現(xiàn)的,具體的容器技術(shù)細(xì)節(jié)在下文討論,這里主要討論的是如何修飾交由容器創(chuàng)建的對(duì)象以及依賴(lài)注入的屬性。
Java中的注解
在Java中,那些要交由Spring容器創(chuàng)建的類(lèi)通過(guò)注解來(lái)修飾,例如JavaWeb中的Controller和Service分別由@Controller和@Service修飾,如下所示。
// 控制器
@Controller
public class SomeController {...}
// 服務(wù)
@Service
public class SomeService {...}
而控制器要通過(guò)服務(wù)來(lái)處理業(yè)務(wù)邏輯,因此Controller依賴(lài)了Service,通過(guò)@Autowired修飾這一屬性,即可完成自動(dòng)注入,如下所示。
@Controller
public class AdministratorController {
@Autowired
private SomeService serv;
...
}
通過(guò)@Autowired注解,IoC容器會(huì)根據(jù)屬性類(lèi)型(SomeService)找到依賴(lài)對(duì)象的實(shí)例,并且注入到Controller的serv屬性。這一過(guò)程中,Controller、Service以及注入均不需要寫(xiě)額外的代碼,只需要通過(guò)注解修飾即可。
OC中的實(shí)現(xiàn)
由于OC沒(méi)有注解特性,因此要標(biāo)記類(lèi)和屬性就需要其他的方法,我經(jīng)過(guò)思考發(fā)現(xiàn)了一種較好的方式,那就是通過(guò)協(xié)議來(lái)標(biāo)記類(lèi),通過(guò)額外的屬性來(lái)標(biāo)記屬性。
- 為了模仿@Controller、@Service等修飾類(lèi)的注解,我們使用IOCComponents協(xié)議來(lái)標(biāo)記類(lèi),來(lái)表示這些類(lèi)交由容器處理。
@interface SGService : NSObject <IOCComponents>
...
@end
- 為了模仿@Autowired實(shí)現(xiàn)的按類(lèi)型注入,我們?cè)谝⑷氲膶傩郧岸嗉右粭l屬性,該條屬性的類(lèi)型為T(mén)ypeAnnotation,我們稱(chēng)之為注解屬性,這個(gè)類(lèi)通過(guò)@Class聲明而并不存在,只是為了標(biāo)記,在反射時(shí)得到的所有屬性都是按照順序排列的,因此每一條注解屬性后的屬性都是需要進(jìn)行依賴(lài)注入的,示例如下。
@interface SGService : NSObject <IOCComponents>
@property (nonatomic, weak, readonly) TypeAnnotation *autowired_0;
@property (nonatomic, strong) SGMapper *mapper;
@end
通過(guò)上文這種方式,在反射時(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開(kāi)始編號(hào)。
OC的一個(gè)實(shí)現(xiàn)示例
這里假設(shè)IoC容器已經(jīng)完成,主要演示流程,容器的技術(shù)細(xì)節(jié)在下文討論。
有一個(gè)Service類(lèi)和一個(gè)Mapper類(lèi),Service依賴(lài)了Mapper,具體代碼如下。
類(lèi)的定義與修飾
- IOCComponents用于標(biāo)識(shí)該類(lèi)由容器負(fù)責(zé)創(chuàng)建
- Autowired(x)表示該屬性按類(lèi)型進(jìn)行依賴(lài)注入
@interface SGService : NSObject <IOCComponents>
Autowired(0)
@property (nonatomic, strong) SGMapper *mapper;
@end
@interface SGMapper : NSObject <IOCComponents>
@end
掃描配置
類(lèi)似于Spring的applicationContext.xml,這里通過(guò)ApplicationContext.plist來(lái)配置要掃描的類(lèi)的特征,目前提供了前綴和類(lèi)列表,對(duì)于有公共前綴的類(lèi)可以配置一個(gè)前綴來(lái)實(shí)現(xiàn)掃描,對(duì)于沒(méi)有前綴的類(lèi),單獨(dú)配置到類(lèi)列表中,如下圖所示。

驗(yàn)證與使用
經(jīng)過(guò)上面的配置,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)建、依賴(lài)查找和依賴(lài)注入,這些功能都需要借助運(yùn)行時(shí)的反射實(shí)現(xiàn),下面將按照容器初始化的過(guò)程來(lái)逐一介紹用到的OC運(yùn)行時(shí)函數(shù),這些函數(shù)均可以在包含objc/runtime.h后使用。
1.IoC容器
容器通過(guò)SGApplicationContext單例來(lái)管理需要容器創(chuàng)建的類(lèi)對(duì)象,通過(guò)一個(gè)Dictionary類(lèi)型的屬性instanceMap存儲(chǔ)對(duì)象是否已經(jīng)創(chuàng)建過(guò),容器中的對(duì)象目前還只是單例的,邏輯很簡(jiǎn)單,每次根據(jù)類(lèi)型取出對(duì)象時(shí),先檢查instanceMap該對(duì)象是否已經(jīng)創(chuàng)建過(guò),如果創(chuàng)建過(guò)則直接返回,否則創(chuàng)建后返回,這里會(huì)通過(guò)運(yùn)行時(shí)函數(shù)objc_getClass檢查該類(lèi)是否已經(jīng)裝載,具體實(shí)現(xiàn)如下。
- (id)getInstanceByClassName:(NSString *)className {
if (self.instanceMap[className] == nil) {
Class clazz = NSClassFromString(className);
// 檢查類(lèi)是否已經(jīng)裝載,防止未定義的類(lèi)實(shí)例化時(shí)出錯(cuò)
if (!objc_getClass(className.UTF8String)) {
return nil;
}
id instance = [clazz new];
self.instanceMap[className] = instance;
}
return self.instanceMap[className];
}
2.掃描類(lèi)列表
通過(guò)函數(shù)objc_getClassList可以獲取類(lèi)列表,該函數(shù)的具體信息如下。
int objc_getClassList(Class *buffer, int bufferCount);
buffer是所有類(lèi)的數(shù)組,bufferCount為數(shù)組大小,返回值也為數(shù)組大小,由于第一次調(diào)用時(shí)并不知道bufferCount,因此可以?xún)纱握{(diào)用,第一次拿到bufferCount,第二次再初始化類(lèi)列表數(shù)組,具體代碼如下,詳細(xì)請(qǐng)見(jiàn)注釋。
- (void)scanClasses {
int classCount = objc_getClassList(NULL, 0);
Class *classList = (Class *)malloc(classCount * sizeof(Class));
classCount = objc_getClassList(classList, classCount);
// 用于存放需要IoC容器處理的類(lèi)的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中的掃描類(lèi)特征配置,只有符合條件的類(lèi)才能被添加
// 第二個(gè)條件檢查IOCComponents標(biāo)記,有標(biāo)記的類(lèi)才被IoC容器處理
if ([self isValidIOCClassNamed:className] && [clazz conformsToProtocol:protocol]) {
[temp addObject:className];
}
}
// 將IoC需要處理的類(lèi)存儲(chǔ)起來(lái)
self.DIClasses = temp;
// 由于類(lèi)列表是malloc創(chuàng)建的,需要手動(dòng)釋放
free(classList);
// 根據(jù)注解屬性處理依賴(lài)注入
[self scanAnnotation];
}
3.掃描類(lèi)屬性與屬性注入
要掃描一個(gè)類(lèi)的所有私有和公有屬性,使用下面的函數(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ì)于名稱(chēng)可以通過(guò)property_getName函數(shù)直接獲得,而其他信息需要先通過(guò)property_getAttributes方法獲得描述屬性的字符串,再進(jìn)行分割。通過(guò)這樣的步驟,即可獲得所有注解屬性的位置,并由此得到所有需要注入的屬性,根據(jù)這些屬性的類(lèi)型進(jìn)行注入即可。
要完成注入,需要先得到代表屬性的Ivar,然后對(duì)實(shí)例進(jìn)行注入,得到Ivar和注入屬性的函數(shù)如下。
// 注意得到Ivar時(shí)的屬性名為實(shí)例變量名而不是property名,例如@property定義的xx,則這里應(yīng)該寫(xiě)作_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容器處理的類(lèi)進(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)、名稱(chēng)(name)和類(lèi)型(type),通過(guò)一個(gè)模型類(lèi)SGAnnotation來(lái)存儲(chǔ)
NSMutableArray *annotations = @[].mutableCopy;
// 保存所有的屬性信息,每個(gè)屬性包含了名稱(chēng)(name)和類(lèi)型(type),通過(guò)一個(gè)模型類(lèi)SGProperty來(lái)存儲(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];
// 這一段代碼用于從描述屬性的字符串中獲取到類(lèi)型,用到了正則和字串處理
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
// 從容器中得到類(lèi)的實(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;
// 得到依賴(lài)對(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é)
通過(guò)這樣的處理,完成了OC語(yǔ)言對(duì)IoC和DI的基本實(shí)現(xiàn),這只是一次探索,還有很多地方需要優(yōu)化和完善。