iOS控制反轉(zhuǎn)(IoC)與依賴(lài)注入(DI)的實(shí)現(xiàn)

背景

最近接觸了一段時(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)列表中,如下圖所示。

ApplicationContext.plist

驗(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)化和完善。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容