[转]一次高效的依赖注入
文章涉及依赖注入方案基于
EXTConcreteProtocol
实现,GitHub链接在这里。
1. 01. 问题场景
如果基于 Cocopods 和 Git Submodules 来做组件化的时候,我们的依赖关系是这样的:
这里依赖路径有两条:
-
- 最简单的主项目依赖第三方 pods。
-
- 组件依赖第三方 pods,主项目再依赖组件。
这种单向的依赖关系,决定了从组件到项目的通讯是单向的,即主项目可以主动向组件发起通讯,但是组件却没有办法主动和主项目通讯。
你可能说不对,可以发通知啊?是的,是可以发通知,但是这一点都不优雅,也不好维护和拓展。
有没有一种更加优雅、更加方便日常开发的拓展和维护的方式呢?答案是有的,名字叫做“依赖注入”。
2. 02. 依赖注入
依赖注入有另外一个名字,叫做“控制反转”,像上面的组件化的例子,主项目依赖组件,现在有一个需求,组件需要依赖主项目,这种情况就叫做“控制反转”。
能把这部分“控制反转”的代码统一起来解耦维护,方便日后拓展和维护的服务,我们就可以叫做依赖注入。
所以依赖注入有两个比较重要的点:
- 第一,要实现这种反转控制的功能。
- 第二,要解耦。
不是我自身的,却是我需要的,都是我所依赖的。一切需要外部提供的,都是需要进行依赖注入的。
这句话出自这篇文章:理解依赖注入与控制反转 | Laravel China 社区 – 高品质的 Laravel 开发者社区
如果对概念性的东西有更加深入的理解,欢迎谷歌搜索“依赖注入”。
3. 03. iOS 依赖注入调查
iOS 平台实现依赖注入功能的开源项目有两个大头:
详细对比发现这两个框架都是严格遵循依赖注入的概念来实现的,并没有将 Objective-C 的 runtime 特性发挥到极致,所以使用起来很麻烦。
还有一点,这两个框架使用继承的方式实现注入功能,对项目的侵入性不容小视。如果你觉得这个侵入性不算什么,那等到你项目大到一定程度,发现之前选择的技术方案有考虑不周,你想切换到其他方案的时候,你一定会后悔当时没选择那个不侵入项目的方案。
那有没有其他没那么方案呢?
GitHub – jspahrsummers/libextobjc: A Cocoa library to extend the Objective-C programming language. 里有一个 EXTConcreteProtocol
虽然没有直接叫做依赖注入,而是叫做混合协议,但是充分使用了 OC 动态语言的特性,不侵入项目,高度自动化,框架十分轻量,使用非常简单。
轻量到什么地步?就只有一个 .h
一个 .m
文件。
简单到什么地步?就只需要一个 @conreteprotocol
关键字,你就已经注入好了。
从一个评价开源框架的方方面面都甩开上面两个框架好几条街。
但是他也有致命的缺点,鱼和熊掌不可兼得,这个我们等会说。
4. 04. EXTConcreteProtocol 实现原理
有两个比较重要的概念需要提前明白才能继续往下将。
-
- 容器。这里的容器是指,我们注入的方法需要有类(class)来装,而装这些方法的器皿就统称为容器。
-
__attribute__()
这是一个 GNU 编译器语法,被constructor
这个关键字修饰的方法会在所有类的+load
方法之后,在main
函数之前被调用。详见:Clang Attributes 黑魔法小记 · sunnyxx的技术博客
如上图,用一句话来描述注入的过程:将待注入的容器中的方法在 load
方法之后 main
函数之前注入指定的类中。
4-1. 04.1. EXTConcreteProtocol 的使用
比方说有一个协议 ObjectProtocol
。我们只要这样写就已经实现了依赖注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="hljs-class"><span class="hljs-keyword">@protocol</span> <span class="hljs-title">ObjectProtocol</span><<span class="hljs-title">NSObject</span>></span> + (<span class="hljs-keyword">void</span>)sayHello; - (<span class="hljs-keyword">int</span>)age; <span class="hljs-keyword">@end</span> @concreteprotocol(ObjectProtocol) + (<span class="hljs-keyword">void</span>)sayHello { <span class="hljs-built_in">NSLog</span>(<span class="hljs-string">@"Hello"</span>); } - (<span class="hljs-keyword">int</span>)age { <span class="hljs-keyword">return</span> <span class="hljs-number">18</span>; } <span class="hljs-keyword">@end</span> |
之后比方说一个 Person
类想要拥有这个注入方法,就只需要遵守这个协议就可以了。
1 2 3 4 |
<span class="hljs-class"><span class="hljs-keyword">@interface</span> <span class="hljs-title">Person</span> : <span class="hljs-title">NSObject</span><<span class="hljs-title">ObjectProtocol</span>></span> <span class="hljs-keyword">@end</span> |
我们接下来就可以对 Person
调用注入的方法。
1 2 3 4 5 6 7 8 9 10 |
<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> * argv[])</span> </span>{ Person *p = [Person <span class="hljs-keyword">new</span>]; NSLog(@<span class="hljs-string">"%@"</span>, [p age]); [p.class sayHello]; } 输出: >>><span class="hljs-number">18</span> >>>Hello |
是不是很神奇?想不想探一下究竟?
4-2. 04.2. 源码解析
先来看一下头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span class="hljs-meta">#define concreteprotocol(NAME) \</span> <span class="hljs-comment">// 定义一个容器类.</span> interface NAME <span class="hljs-meta">## _ProtocolMethodContainer : NSObject <span class="hljs-meta-string">< NAME ></span> {} \</span> <span class="hljs-keyword">@end</span> \ \ <span class="hljs-class"><span class="hljs-keyword">@implementation</span> <span class="hljs-title">NAME</span> ## <span class="hljs-title">_ProtocolMethodContainer</span> \</span> <span class="hljs-comment">// load 方法添加混合协议.</span> + (<span class="hljs-keyword">void</span>)load { \ <span class="hljs-keyword">if</span> (!ext_addConcreteProtocol(objc_getProtocol(metamacro_stringify(NAME)), <span class="hljs-keyword">self</span>)) \ fprintf(stderr, <span class="hljs-string">"ERROR: Could not load concrete protocol %s\n"</span>, metamacro_stringify(NAME)); \ } \ <span class="hljs-comment">// load 之后, main 之前执行方法注入.</span> __attribute__((constructor)) \ <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> ext_ <span class="hljs-meta">## NAME ## _inject (void) { \</span> ext_loadConcreteProtocol(objc_getProtocol(metamacro_stringify(NAME))); \ } <span class="hljs-comment">// load 方法添加混合协议.</span> <span class="hljs-built_in">BOOL</span> ext_addConcreteProtocol (Protocol *protocol, Class methodContainer); <span class="hljs-comment">// load 之后, main 之前执行方法注入.</span> <span class="hljs-keyword">void</span> ext_loadConcreteProtocol (Protocol *protocol); |
可以在源码中清楚看到 concreteprotocol
这个宏定义为我们的协议添加了一个容器类,我们主要注入的比如 +sayHello
和 -age
方法都被定义在这个容器类之中。
然后在 +load
方法中调用了 ext_addConcreteProtocol
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
<span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> { <span class="hljs-comment">// 用户定义的协议.</span> __<span class="hljs-keyword">unsafe_unretained</span> Protocol *protocol; <span class="hljs-comment">// 在 __attribute__((constructor)) 时往指定类里注入方法的 block.</span> <span class="hljs-keyword">void</span> *injectionBlock; <span class="hljs-comment">// 对应的协议是否已经准备好注入.</span> <span class="hljs-built_in">BOOL</span> ready; } EXTSpecialProtocol; <span class="hljs-built_in">BOOL</span> ext_addConcreteProtocol (Protocol *protocol, Class containerClass) { <span class="hljs-keyword">return</span> ext_loadSpecialProtocol(protocol, ^(Class destinationClass){ ext_injectConcreteProtocol(protocol, containerClass, destinationClass); }); } <span class="hljs-built_in">BOOL</span> ext_loadSpecialProtocol (Protocol *protocol, <span class="hljs-keyword">void</span> (^injectionBehavior)(Class destinationClass)) { <span class="hljs-keyword">@autoreleasepool</span> { <span class="hljs-built_in">NSCParameterAssert</span>(protocol != <span class="hljs-literal">nil</span>); <span class="hljs-built_in">NSCParameterAssert</span>(injectionBehavior != <span class="hljs-literal">nil</span>); <span class="hljs-comment">// 加锁</span> <span class="hljs-keyword">if</span> (pthread_mutex_lock(&specialProtocolsLock) != <span class="hljs-number">0</span>) { fprintf(stderr, <span class="hljs-string">"ERROR: Could not synchronize on special protocol data\n"</span>); <span class="hljs-keyword">return</span> <span class="hljs-literal">NO</span>; } <span class="hljs-comment">// specialProtocols 是一个链表,每个协议都会被组织成为一个 EXTSpecialProtocol,这个 specialProtocols 里存放了了这些 specialProtocols.</span> <span class="hljs-keyword">if</span> (specialProtocolCount >= specialProtocolCapacity) { ... } <span class="hljs-meta">#ifndef __clang_analyzer__</span> ext_specialProtocolInjectionBlock copiedBlock = [injectionBehavior <span class="hljs-keyword">copy</span>]; <span class="hljs-comment">// 将协议保存为一个 EXTSpecialProtocol 结构体.</span> specialProtocols[specialProtocolCount] = (EXTSpecialProtocol){ .protocol = protocol, .injectionBlock = (__bridge_retained <span class="hljs-keyword">void</span> *)copiedBlock, .ready = <span class="hljs-literal">NO</span> }; <span class="hljs-meta">#endif</span> ++specialProtocolCount; pthread_mutex_unlock(&specialProtocolsLock); } <span class="hljs-keyword">return</span> <span class="hljs-literal">YES</span>; } |
我们的 ext_loadSpecialProtocol
方法里传进去一个 block,这个 block 里调用了 ext_injectConcreteProtocol
这个方法。
ext_injectConcreteProtocol
这个方法接受三个参数,第一个是协议,就是我们要注入的方法的协议;第二个是容器类,就是框架为我们添加的那个容器;第三个参数是目标注入类,就是我们要把这个容器里的方法注入到哪个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ext_injectConcreteProtocol</span> <span class="hljs-params">(Protocol *protocol, Class containerClass, Class class)</span> </span>{ <span class="hljs-comment">// 获取容器类里所有的实例方法.</span> <span class="hljs-keyword">unsigned</span> imethodCount = <span class="hljs-number">0</span>; Method *imethodList = class_copyMethodList(containerClass, &imethodCount); <span class="hljs-comment">// 获取容器类里所有的类方法方法.</span> <span class="hljs-keyword">unsigned</span> cmethodCount = <span class="hljs-number">0</span>; Method *cmethodList = class_copyMethodList(object_getClass(containerClass), &cmethodCount); <span class="hljs-comment">// 拿到要注入方法的类的元类.</span> Class metaclass = object_getClass(class); <span class="hljs-comment">// 注入实例方法.</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">unsigned</span> methodIndex = <span class="hljs-number">0</span>;methodIndex < imethodCount;++methodIndex) { Method method = imethodList[methodIndex]; SEL selector = method_getName(method); <span class="hljs-comment">// 如果该类已经实现了这个方法,就跳过注入,不至于覆盖用户自定义的实现.</span> <span class="hljs-keyword">if</span> (class_getInstanceMethod(class, selector)) { <span class="hljs-keyword">continue</span>; } IMP imp = method_getImplementation(method); <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *types = method_getTypeEncoding(method); <span class="hljs-keyword">if</span> (!class_addMethod(class, selector, imp, types)) { <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"ERROR: Could not implement instance method -%s from concrete protocol %s on class %s\n"</span>, sel_getName(selector), protocol_getName(protocol), class_getName(class)); } } <span class="hljs-comment">// 注入类方法.</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">unsigned</span> methodIndex = <span class="hljs-number">0</span>;methodIndex < cmethodCount;++methodIndex) { Method method = cmethodList[methodIndex]; SEL selector = method_getName(method); <span class="hljs-comment">// +initialize 不能被注入.</span> <span class="hljs-keyword">if</span> (selector == @selector(initialize)) { <span class="hljs-keyword">continue</span>; } <span class="hljs-comment">// 如果该类已经实现了这个方法,就跳过注入,不至于覆盖用户自定义的实现.</span> <span class="hljs-keyword">if</span> (class_getInstanceMethod(metaclass, selector)) { <span class="hljs-keyword">continue</span>; } IMP imp = method_getImplementation(method); <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *types = method_getTypeEncoding(method); <span class="hljs-keyword">if</span> (!class_addMethod(metaclass, selector, imp, types)) { <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"ERROR: Could not implement class method +%s from concrete protocol %s on class %s\n"</span>, sel_getName(selector), protocol_getName(protocol), class_getName(class)); } } <span class="hljs-comment">// 管理内存</span> <span class="hljs-built_in">free</span>(imethodList); imethodList = <span class="hljs-literal">NULL</span>; <span class="hljs-built_in">free</span>(cmethodList); cmethodList = <span class="hljs-literal">NULL</span>; <span class="hljs-comment">// 允许用户在容器类里复写 +initialize 方法,这里调用是保证用户复写的实现能够被执行.</span> (<span class="hljs-keyword">void</span>)[containerClass <span class="hljs-class"><span class="hljs-keyword">class</span>];</span> } |
我们再看一下在 +load
之后 main
之前调用的 ext_loadConcreteProtocol
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">ext_loadConcreteProtocol</span> <span class="hljs-params">(Protocol *protocol)</span> </span>{ ext_specialProtocolReadyForInjection(protocol); } <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">ext_specialProtocolReadyForInjection</span> <span class="hljs-params">(Protocol *protocol)</span> </span>{ @autoreleasepool { NSCParameterAssert(protocol != nil); <span class="hljs-comment">// 加锁</span> <span class="hljs-keyword">if</span> (pthread_mutex_lock(&specialProtocolsLock) != <span class="hljs-number">0</span>) { <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"ERROR: Could not synchronize on special protocol data\n"</span>); <span class="hljs-keyword">return</span>; } <span class="hljs-comment">// 检查要对应的 protocol 是否已经加载进上面的链表中了,如果找到了,就将对应的 EXTSpecialProtocol 结构体的 ready 置为 YES.</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>;i < specialProtocolCount;++i) { <span class="hljs-keyword">if</span> (specialProtocols[i].protocol == protocol) { <span class="hljs-keyword">if</span> (!specialProtocols[i].ready) { specialProtocols[i].ready = YES; assert(specialProtocolsReady < specialProtocolCount); <span class="hljs-keyword">if</span> (++specialProtocolsReady == specialProtocolCount) <span class="hljs-comment">// 如果所有的 EXTSpecialProtocol 结构体都准备好了,就开始执行注入.</span> ext_injectSpecialProtocols(); } <span class="hljs-keyword">break</span>; } } pthread_mutex_unlock(&specialProtocolsLock); } } |
上面都是准备工作,接下来开始进入核心方法进行注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ext_injectSpecialProtocols</span> <span class="hljs-params">(<span class="hljs-keyword">void</span>)</span> </span>{ <span class="hljs-comment">// 对协议进行排序.</span> <span class="hljs-comment">// 比方说 A 协议继承自 B 协议,但是不一定是 B 协议对应的容器类的 load 方法先执行,A 的后执行. 所以如果 B 协议的类方法中复写了 A 协议中的方法,那么应该保证 B 协议复写的方法被注入,而不是 A 协议的容器方法的实现.</span> <span class="hljs-comment">// 为了保证这个循序,所以要对协议进行排序,上面说的 A 继承自 B,那么循序应该是 A 在 B 前面.</span> qsort_b(specialProtocols, specialProtocolCount, <span class="hljs-keyword">sizeof</span>(EXTSpecialProtocol), ^(<span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> *a, <span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> *b){ <span class="hljs-keyword">if</span> (a == b) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; <span class="hljs-keyword">const</span> EXTSpecialProtocol *protoA = a; <span class="hljs-keyword">const</span> EXTSpecialProtocol *protoB = b; <span class="hljs-keyword">int</span> (^protocolInjectionPriority)(<span class="hljs-keyword">const</span> EXTSpecialProtocol *) = ^(<span class="hljs-keyword">const</span> EXTSpecialProtocol *specialProtocol){ <span class="hljs-keyword">int</span> runningTotal = <span class="hljs-number">0</span>; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>;i < specialProtocolCount;++i) { <span class="hljs-keyword">if</span> (specialProtocol == specialProtocols + i) <span class="hljs-keyword">continue</span>; <span class="hljs-keyword">if</span> (protocol_conformsToProtocol(specialProtocol->protocol, specialProtocols[i].protocol)) runningTotal++; } <span class="hljs-keyword">return</span> runningTotal; }; <span class="hljs-keyword">return</span> protocolInjectionPriority(protoB) - protocolInjectionPriority(protoA); }); <span class="hljs-comment">// 获取项目中所有的类 😭😭😭.</span> <span class="hljs-keyword">unsigned</span> classCount = objc_getClassList(<span class="hljs-literal">NULL</span>, <span class="hljs-number">0</span>); <span class="hljs-keyword">if</span> (!classCount) { <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"ERROR: No classes registered with the runtime\n"</span>); <span class="hljs-keyword">return</span>; } Class *allClasses = (Class *)<span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(Class) * (classCount + <span class="hljs-number">1</span>)); <span class="hljs-keyword">if</span> (!allClasses) { <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"ERROR: Could not allocate space for %u classes\n"</span>, classCount); <span class="hljs-keyword">return</span>; } classCount = objc_getClassList(allClasses, classCount); @autoreleasepool { <span class="hljs-comment">// 遍历所有的要注入的协议结构体.</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>;i < specialProtocolCount;++i) { Protocol *protocol = specialProtocols[i].protocol; <span class="hljs-comment">// 使用 __bridge_transfer 把对象的内存管理交给 ARC.</span> ext_specialProtocolInjectionBlock injectionBlock = (__bridge_transfer id)specialProtocols[i].injectionBlock; specialProtocols[i].injectionBlock = <span class="hljs-literal">NULL</span>; <span class="hljs-comment">// 遍历所有的类 😭😭😭.</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">unsigned</span> classIndex = <span class="hljs-number">0</span>;classIndex < classCount;++classIndex) { Class <span class="hljs-class"><span class="hljs-keyword">class</span> = <span class="hljs-title">allClasses</span>[<span class="hljs-title">classIndex</span>];</span> <span class="hljs-comment">// 如果这个类遵守了要注入的协议,那么就执行注入.</span> <span class="hljs-comment">// 注意: 这里是 continue 不是 break,因为一个类可以注入多个协议的方法.</span> <span class="hljs-keyword">if</span> (!class_conformsToProtocol(class, protocol)) <span class="hljs-keyword">continue</span>; injectionBlock(class); } } } <span class="hljs-comment">// 管理内存.</span> <span class="hljs-built_in">free</span>(allClasses); <span class="hljs-built_in">free</span>(specialProtocols); specialProtocols = <span class="hljs-literal">NULL</span>; specialProtocolCount = <span class="hljs-number">0</span>; specialProtocolCapacity = <span class="hljs-number">0</span>; specialProtocolsReady = <span class="hljs-number">0</span>; } |
这一路看下来,原理看的明明白白,是不是也没什么特别的,都是 runtime 的知识。但是这个思路确实是 666。
4-3. 04.3. 问题在哪?
这不挺好的吗?别人也分析过这个框架的源码,我再写一遍有什么意义?
这问题挺好,确实是这样,如果一切顺利,我这篇文章没有存在的意义。接下来看一下问题出现在哪?
看到我刚才的注释了吗?这个笑脸很灿烂。如果项目不大,比如项目只有几百个类,这些都没有问题的,但是我们项目有接近 30000 个类,没错,是三万。我们使用注入的地方有几十上百处,两套 for 循环算下来是一个百万级别的。而且 objc_getClassList
这个方法是非常耗时的而且没有缓存。
1 2 3 |
<span class="hljs-comment">// 获取项目中所有的类 😭😭😭.</span> <span class="hljs-comment">// 遍历所有的类 😭😭😭.</span> |
在贝聊项目上,这个方法在我的 iPhone 6s Plus 上要耗时一秒,在更老的 iPhone 6 上耗时要 3 秒,iPhone 5 可以想象要更久。而且随着项目迭代,项目中的类会越来越多, 这个耗时也会越来越长。
这个耗时是 pre-main 耗时,就是用户看那个白屏启动图的时候在做这个操作,严重影响用户体验。我们的产品就因为这个点导致闪屏广告展示出现问题,直接影响业务。
5. 05. 解决方案
从上面的分析可以知道,导致耗时的原因就是原框架获取所有的类进行遍历。其实这是一个自动化的牛逼思路,这也是这个框架高于前面两个框架的核心原因。但是因为项目规模的原因导致这个点成为了实践中的短板,这也是作者始料未及的。
那我们怎么优化这个点呢?因为要注入方法的类没有做其他的标记,只能扫描所有的类,找到那些遵守了这个协议的再进行注入,这是要注入的类和注入行为的唯一联系点。从设计的角度来说,如果要主动实现注入,确实是这样的,没有更好方案来实现相同的功能。
但是有一个下策,能显著提高这部分性能,就是退回到上面两个框架所做的那样,让用户自己去标识哪些类需要注入。这样我把这些需要注入的类放到一个集合里,遍历注入,这样做性能是最好的。如果我从头设计一个方案,这也是不错的选择。
但是我现在做不了这些,我项目里有好几百个地方用了注入,如果我采用上面的方式,我要改好几百个地方。这样做很低效,而且我也不能保证我眼睛不会花出个错。我只能选择自动化去做这个事。
如果换个思路,我不主动注入,我懒加载,等你调用注入的方法我再执行注入操作呢?如果能实现这个,那问题就解决了。
-
- 开始我们仍然在
+load
方法中做准备工作,和原有的实现一样,把所有的协议都存到链表中。
- 开始我们仍然在
-
- 在
__attribute__((constructor))
中仍然做是否能执行注入的检查。
- 在
-
- 现在我们 hook
NSObject
的+resolveInstanceMethod:
和+resolveClassMethod:
。
- 现在我们 hook
-
- 在 hook 中进行检查,如果该类有遵守了我们实现了注入的协议,那么就给该类注入容器中的方法。
[resource]一次高效的依赖注入