背景

最近接触了一段时间的SpringMVC,对其控制反转(IoC)和依赖注入(DI)印象深刻,此后便一直在思考如何使用OC语言较好的实现这两个功能。Java语言自带的注解特性为IoC和DI带来了极大的方便,要在OC上较好的实现这两个功能,需要一些小小的技巧。

控制反转和依赖注入

控制反转

简单来说,将一个类对象的创建由手动new方式改为从IOC容器内获取,就是一种控制反转,例如我们现在要创建一个ClassA类,则常规方法为

ClassA *a = [ClassA new];

如果使用控制反转,则从容器内获取ClassA对象,对象由容器负责创建。

ApplicationContext *context = [ApplicationContext sharedContext];
ClassA *a = [context getInstanceByClassName:@"ClassA"];

依赖注入

所谓依赖注入,是指一个类对象不应负责去查找其依赖属性,而是交由容器去处理。例如ClassA类有一个ClassB类对象,如下所示。

@interface ClassA : NSObject

@property(nonatomic, strong) ClassB *b;

@end

常规情况下,要将ClassB的对象创建后传递给ClassA,如下所示。

ClassA *a = [ClassA new];
ClassB *b = [ClassB new];
a.b = b;

如果交由容器处理,则容器会自动创建ClassA、ClassB,并且根据ClassA的依赖属性类型ClassB自动的将ClassB的实例注入到ClassA对象中。

优点

使用控制反转和依赖注入将对象的创建与依赖对象的注入交由IoC容器来完成,这样做不仅能够降低代码的耦合度,更可以减少代码量。

类与属性的修饰

这两个功能都是在运行时通过反射来实现的,具体的容器技术细节在下文讨论,这里主要讨论的是如何修饰交由容器创建的对象以及依赖注入的属性。

Java中的注解

在Java中,那些要交由Spring容器创建的类通过注解来修饰,例如JavaWeb中的Controller和Service分别由@Controller和@Service修饰,如下所示。

// 控制器
@Controller
public class SomeController {...}
// 服务
@Service
public class SomeService {...}

而控制器要通过服务来处理业务逻辑,因此Controller依赖了Service,通过@Autowired修饰这一属性,即可完成自动注入,如下所示。

@Controller
public class AdministratorController {
@Autowired
private SomeService serv;
...
}

通过@Autowired注解,IoC容器会根据属性类型(SomeService)找到依赖对象的实例,并且注入到Controller的serv属性。这一过程中,Controller、Service以及注入均不需要写额外的代码,只需要通过注解修饰即可。

OC中的实现

由于OC没有注解特性,因此要标记类和属性就需要其他的方法,我经过思考发现了一种较好的方式,那就是通过协议来标记类,通过额外的属性来标记属性。

* 为了模仿@Controller、@Service等修饰类的注解,我们使用IOCComponents协议来标记类,来表示这些类交由容器处理。

@interface SGService : NSObject <IOCComponents>
...
@end
  • 为了模仿@Autowired实现的按类型注入,我们在要注入的属性前多加一条属性,该条属性的类型为TypeAnnotation,我们称之为注解属性,这个类通过@Class声明而并不存在,只是为了标记,在反射时得到的所有属性都是按照顺序排列的,因此每一条注解属性后的属性都是需要进行依赖注入的,示例如下。
@interface SGService : NSObject <IOCComponents>

@property (nonatomic, weak, readonly) TypeAnnotation *autowired_0;
@property (nonatomic, strong) SGMapper *mapper; @end

通过上文这种方式,在反射时autowired_0和mapper是相邻的,因此可以判断出mapper需要注入,只需要反射出该property的信息,并且从容器中查找,并注入即可,为了方便定义这样的注解属性,我们使用一个宏如下所示。

#define Autowired(num) @property (nonatomic, weak, readonly) TypeAnnotation *autowired_##num;

那么上面的代码可以进行如下的简化。

@interface SGService : NSObject <IOCComponents>

Autowired(0)
@property (nonatomic, strong) SGMapper *mapper; @end

这里的数字是为了多个注解造成属性的重复定义,可以从0开始编号。

OC的一个实现示例

这里假设IoC容器已经完成,主要演示流程,容器的技术细节在下文讨论。

有一个Service类和一个Mapper类,Service依赖了Mapper,具体代码如下。

类的定义与修饰

  • IOCComponents用于标识该类由容器负责创建
  • Autowired(x)表示该属性按类型进行依赖注入
@interface SGService : NSObject <IOCComponents>

Autowired(0)
@property (nonatomic, strong) SGMapper *mapper; @end
@interface SGMapper : NSObject <IOCComponents>

@end

扫描配置

类似于Spring的applicationContext.xml,这里通过ApplicationContext.plist来配置要扫描的类的特征,目前提供了前缀和类列表,对于有公共前缀的类可以配置一个前缀来实现扫描,对于没有前缀的类,单独配置到类列表中,如下图所示。

验证与使用

经过上面的配置,IoC容器便可完成Service与Mapper的创建,以及将Mapper注入到Service,验证如下。

- (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容器的实现

IoC容器的核心功能是对象创建、依赖查找和依赖注入,这些功能都需要借助运行时的反射实现,下面将按照容器初始化的过程来逐一介绍用到的OC运行时函数,这些函数均可以在包含objc/runtime.h后使用。

1.IoC容器

容器通过SGApplicationContext单例来管理需要容器创建的类对象,通过一个Dictionary类型的属性instanceMap存储对象是否已经创建过,容器中的对象目前还只是单例的,逻辑很简单,每次根据类型取出对象时,先检查instanceMap该对象是否已经创建过,如果创建过则直接返回,否则创建后返回,这里会通过运行时函数objc_getClass检查该类是否已经装载,具体实现如下。

- (id)getInstanceByClassName:(NSString *)className {
if (self.instanceMap[className] == nil) {
Class clazz = NSClassFromString(className);
// 检查类是否已经装载,防止未定义的类实例化时出错
if (!objc_getClass(className.UTF8String)) {
return nil;
}
id instance = [clazz new];
self.instanceMap[className] = instance;
}
return self.instanceMap[className];
}

2.扫描类列表

通过函数objc_getClassList可以获取类列表,该函数的具体信息如下。

int objc_getClassList(Class *buffer, int bufferCount);

buffer是所有类的数组,bufferCount为数组大小,返回值也为数组大小,由于第一次调用时并不知道bufferCount,因此可以两次调用,第一次拿到bufferCount,第二次再初始化类列表数组,具体代码如下,详细请见注释。

- (void)scanClasses {
int classCount = objc_getClassList(NULL, 0);
Class *classList = (Class *)malloc(classCount * sizeof(Class));
classCount = objc_getClassList(classList, classCount);
// 用于存放需要IoC容器处理的类的OC数组
NSMutableArray *temp = @[].mutableCopy;
// 获得IOCComponents协议,用于判断标记
Protocol *protocol = objc_getProtocol("IOCComponents");
for (int i = 0; i < classCount; i++) {
Class clazz = classList[i];
NSString *className = NSStringFromClass(clazz);
// 第一个判断条件对应于ApplicationContext.plist中的扫描类特征配置,只有符合条件的类才能被添加
// 第二个条件检查IOCComponents标记,有标记的类才被IoC容器处理
if ([self isValidIOCClassNamed:className] && [clazz conformsToProtocol:protocol]) {
[temp addObject:className];
}
}
// 将IoC需要处理的类存储起来
self.DIClasses = temp;
// 由于类列表是malloc创建的,需要手动释放
free(classList);
// 根据注解属性处理依赖注入
[self scanAnnotation];
}

3.扫描类属性与属性注入

要扫描一个类的所有私有和公有属性,使用下面的函数。

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);

该方法可以列出所有属性,先私有后公有,按照定义的顺序从上到下列出,其返回值是一个objc_property_t结构体数组,从objc_property_t结构体中可以得到属性的各种信息,对于名称可以通过property_getName函数直接获得,而其他信息需要先通过property_getAttributes方法获得描述属性的字符串,再进行分割。通过这样的步骤,即可获得所有注解属性的位置,并由此得到所有需要注入的属性,根据这些属性的类型进行注入即可。

要完成注入,需要先得到代表属性的Ivar,然后对实例进行注入,得到Ivar和注入属性的函数如下。

// 注意得到Ivar时的属性名为实例变量名而不是property名,例如@property定义的xx,则这里应该写作_xx
Ivar class_getInstanceVariable(Class cls, const char *name);
// 将value注入到obj实例的 ivar实例变量上
void object_setIvar(id obj, Ivar ivar, id value);

具体代码如下,详细请看注释。

- (void)scanAnnotation {
// 对scanClasses中得到的需要IoC容器处理的类进行遍历
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),通过一个模型类SGAnnotation来存储
NSMutableArray *annotations = @[].mutableCopy;
// 保存所有的属性信息,每个属性包含了名称(name)和类型(type),通过一个模型类SGProperty来存储
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];
// 如果当前属性为注解属性,则记录进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
// 从容器中得到类的实例
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;
// 得到依赖对象,并注入到注解修饰的属性
id destInstance = [self getInstanceByClassName:typeName];
Ivar ivar = class_getInstanceVariable([diInstance class], [NSString stringWithFormat:@"_%@",varName].UTF8String);
object_setIvar(diInstance, ivar, destInstance);
}
} // scan classes end
}

总结

通过这样的处理,完成了OC语言对IoC和DI的基本实现,这只是一次探索,还有很多地方需要优化和完善。

iOS控制反转(IoC)与依赖注入(DI)的实现的更多相关文章

  1. 控制反转IOC与依赖注入DI

    理解 IOC  http://www.cnblogs.com/zhangchenliang/archive/2013/01/08/2850970.html IOC 相关实例      的http:// ...

  2. 控制反转(Ioc)和依赖注入(DI)

    控制反转IOC, 全称 “Inversion of Control”.依赖注入DI, 全称 “Dependency Injection”. 面向的问题:软件开发中,为了降低模块间.类间的耦合度,提倡基 ...

  3. 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI) 依赖注入和控制反转的理解,写的太好了。

    轻松学,浅析依赖倒置(DIP).控制反转(IOC)和依赖注入(DI) 2017年07月13日 22:04:39 frank909 阅读数:14269更多 所属专栏: Java 反射基础知识与实战   ...

  4. 控制反转IOC与依赖注入DI【转】

    转自:http://my.oschina.net/1pei/blog/492601 一直对控制反转.依赖注入不太明白,看到这篇文章感觉有点懂了,介绍的很详细. 1. IoC理论的背景我们都知道,在采用 ...

  5. 【转载】浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)

    原文地址 http://blog.csdn.net/briblue/article/details/75093382 写这篇文章的原因是这两天在编写关于 Dagger2 主题的博文时,花了大量的精力来 ...

  6. 控制反转IOC与依赖注入DI - 理论篇

    学无止境,精益求精 十年河东十年河西,莫欺少年穷 昨天是五一小长假归来上班的第一天,身体疲劳,毫无工作热情.于是就看看新闻,喝喝茶,荒废了一天 也就在昨天,康美同事张晶童鞋让我学习下IOC的理论及实现 ...

  7. 依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)

    原文: https://blog.csdn.net/briblue/article/details/75093382 写这篇文章的原因是这两天在编写关于 Dagger2 主题的博文时,花了大量的精力来 ...

  8. 20181123_控制反转(IOC)和依赖注入(DI)

    一.   控制反转和依赖注入: 控制反转的前提, 是依赖倒置原则, 系统架构时,高层模块不应该依赖于低层模块,二者通过抽象来依赖 (依赖抽象,而不是细节) 如果要想做到控制反转(IOC), 就必须要使 ...

  9. Spring框架学习笔记(1)——控制反转IOC与依赖注入DI

    Spring框架的主要作用,就是提供了一个容器,使用该容器就可以创建并管理对象.比如说Dao类等,又或者是具有多依赖关系的类(Student类中包含有Teacher类的成员变量) Spring有两个核 ...

随机推荐

  1. line-height与图片底部间隙的学习整理转述

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 看大牛张鑫旭的视屏可能会理解的更深一些,点击这里: line-height,两行文字的基线之间的距离: 基 ...

  2. sumo快速运行简单仿真实例详细教程

    本文旨在让大家快速的了解sumo,并给出运行一个简单的sumo的例子的教程,进而了解基本sumo工程的架构,使大家对该软件产生兴趣并持续学习下去,刚开始学习仿真的确枯燥,项目"跑起来&quo ...

  3. .Net Core 通过依赖注入和动态加载程序集实现宿程序和接口实现类库完全解构

    网上很多.Net Core依赖注入的例子代码,例如再宿主程序中要这样写: services.AddTransient<Interface1, Class1>(); 其中Interface1 ...

  4. C#生成MD5码

    /// <summary> /// 获取文件的MD5码 /// </summary> /// <param name="fileName">传入 ...

  5. LeetCode Binary Search Summary 二分搜索法小结

    二分查找法作为一种常见的查找方法,将原本是线性时间提升到了对数时间范围,大大缩短了搜索时间,具有很大的应用场景,而在LeetCode中,要运用二分搜索法来解的题目也有很多,但是实际上二分查找法的查找目 ...

  6. 多线程利器---队列(queue)

    列表是不安全的数据结构 import threading,time li=[1,2,3,4,5] def pri(): while li: a=li[-1] print(a) time.sleep(1 ...

  7. css 宽高自适应的div 元素 如何居中 垂直居中

    在我们 编写css 样式的时候经常会遇见一个问题 那就是一个 宽高未知的元素 要让他 垂直居中如何实现这个呢 下面是我常用的两种方法 上代码 下面的是 结构代码 <div class=" ...

  8. C++11的value category(值类别)以及move semantics(移动语义)

    转载请保留以下声明 作者:赵宗晟 出处:http://www.cnblogs.com/zhao-zongsheng/p/value_categories_and_move_semantics.html ...

  9. pyqt5 动画学习(四) 旋转动画,使用QGraphicsView让自己的控件旋转起来

    今天学有所成,赶紧记下今天的成果 之前三篇文章分别演示了空间的大小改变,移动,及颜色变化.在后续研究旋转的过程中即为艰难 如果你是使用pyqt4,那么使用QGraphicsItemAnimation便 ...

  10. ios开发-日期处理(类似朋友圈,微博等的发送时间)

    ios开发中,我们经常要处理从服务器获取的时间.类似朋友圈,微博这些应用.我们经常可以看到“刚刚”,“31分钟前发表”,“昨天5点”,之类的字样. 当时我们从服务器端获取的都是那条朋友圈信息,或者微博 ...