iOS依赖注入:技术原理+应用场景,四种方式+源码解读

4,700 阅读8分钟

前提:闲来有意,偶作文章。

摘要:依赖注入(dependency injection,缩写为 DI)是一种软件设计模式,也是实现控制反转的其中一种技术。这种模式能让一个物件接收它所依赖的其他物件。“依赖”是指接收方所需的对象。“注入”是指将“依赖”传递给接收方的过程。在“注入”之后,接收方才会调用该“依赖”。

  • 第一种:使用协议结耦,进行依赖注入。容器Container进行类/协议注入,讲述以Swinject为例。

(一)、类/协议注册+关联对象实例的通用方法入口:

@discardableResult
public func register<Service>(
    _ serviceType: Service.Type,
    name: String? = nil,
    factory: @escaping(Resolver) -> Service
) -> ServiceEntry<Service> {
    return _register(serviceType, factory: factory, name: name)
}

1.1,在此方法内部实现,维护了一个以ServiceKey类型为key,ServiceEntry类型为value的字典services,ServiceEntry实例持有了上述方法的factory-closure闭包(此closure闭包一般用于创建类实例对象)。

1.2 在register之后可以通过设置.inObjectScope(##ObjectScope##)的方式,指定此次注册的(协议)类关联的实例对象拥有什么level的场景。对于level的设定,是在ServiceEntry内部实现,代码如下:

internal lazy var storage: InstanceStorage = { [unowned self] in
    self.objectScope.makeStorage() 
}() 

/// Will invoke and return the result of `storageFactory` closure provided during initialisation.
public func makeStorage() -> InstanceStorage {
    if let parent = parent {
        return CompositeStorage([storageFactory(), parent.makeStorage()])
    } else {
        return storageFactory()
    }
}

//这里storageFactory是一个closure,对应level明细中的ObjectScope(storageFactory:传入的init方法
private var storageFactory: () -> InstanceStorage

level明细:

extension ObjectScope {
    /// A new instance is always created by the ``Container`` when a type is resolved.
    /// The instance is not shared.
    public static let transient = ObjectScope(storageFactory: TransientStorage.init, description: "transient")

    /// Instances are shared only when an object graph is being created,
    /// otherwise a new instance is created by the ``Container``. This is the default scope.
    public static let graph = ObjectScope(storageFactory: GraphStorage.init, description: "graph")

    /// An instance provided by the ``Container`` is shared within the ``Container`` and its child `Containers`.
    public static let container = ObjectScope(storageFactory: PermanentStorage.init, description: "container")

    /// An instance provided by the ``Container`` is shared within the ``Container`` and its child ``Container``s
    /// as long as there are strong references to given instance. Otherwise new instance is created

    /// when resolving the type.
    public static let weak = ObjectScope(storageFactory: WeakStorage.init, description: "weak", parent: ObjectScope.graph)
}

(二)、通过类协议取实例对象。

整个过程与上述呼应,建立ServiceKey对象作为key,通过entry = getEntry(for: key)操作services字典取出entry对象。接着进行以下操作

func resolve<Service, Factory>(entry: ServiceEntryProtocol, invoker: (Factory) -> Any) -> Service? {
    incrementResolutionDepth()
    //defer是在整个方法域执行结束前执行,例如在此就是在return后的方法执行完后,才会执行defer内的方法。此关键字有点文章,在此不赘述,结尾会放一个case。
    defer { decrementResolutionDepth() }
    guard let currentObjectGraph = currentObjectGraph else {
        fatalError("If accessing container from multiple threads, make sure to use a synchronized resolver.")
    }
    //判断是否已经存在persistedInstance,存在即从instances取出
    if let persistedInstance = persistedInstance(Service.self, from: entry, in: currentObjectGraph) {
     //取实例源码:entry.storage.instance(inGraph: graph) as? Service
        return persistedInstance
    }
    //这里呼应上面的register方法中的ServiceEntry持有的factory-closure闭包。
    let resolvedInstance = invoker(entry.factory as! Factory)
    if let persistedInstance = persistedInstance(Service.self, from: entry, in: currentObjectGraph) {
        // An instance for the key might be added by the factory invocation.
        return persistedInstance
    }
    //内部均涉及对InstanceStorage.instance赋值操作,不同level操作逻辑不同
    entry.storage.setInstance(resolvedInstance as Any, inGraph: currentObjectGraph)
    
   //code...
   
    return resolvedInstance as? Service
}
  • 第二种:监听者遵从协议 + 【+load】方法注入监听者 + AssicationManager存储类管理。

在原理上本质也是用了Manager管理类的概念,同时与VC进行了一对一的owner持有者唯一关系绑定。在manager管理类内部进行监听者类的收集,集合方式为NSMutableArray<Class<InjectListenerProtocol>>InjectListenerProtocol是想实施监听的类所遵守的Base协议。Base协议有多个子协议,根据不同通用功能的vc定义不同的继承协议。例如:UIViewController,UINavigatonContronller,UITabBarViewController,以及甚至UIWindonw级别的只要需要被监听且具备明确的生命周期步骤均可使用。

实现过程简述:假如我们实现一个Manager类,需要监听TestVC的willAppear和didDisapper时机。 1,在Manager类做以下:

(1),+load方法内部进行注入[TestVC injectListenerClass:self]

(2),遵守InjectListenerProtocol.Type类的协议;

(3),实现协议的与willAppear与didDisapper对应的方法;

2,在TestVC类内需要基于不同的生命周期的方法内部进行监听者的遍历,通过遍历对监听者遵守的协议的方法进行回调。

对于上述的过程,读者可能会有一个疑问就是:监听者和被监听者的是怎么被绑定到一起的。如果实现大胆些,那这些应该够用了。

场景:基于生命周期的监听,可以直接在单例内部进行操作,通过协议内监听方法回调触发时机

  • 第三种:业务类遵守协议 + 路由类建立协议集合类 + 路由类注册消费者。

以本人项目为例,内容会尽可能的以文字表述,但会贴出关键技术代码,因为涉及项目技术保密,所以会写伪代码替代。(此技术不接受讨论,抱歉)

实现原理简述分三部分:

1,+load注册消费者。

+load方法的加载时机是在loadImages阶段,大致如下:

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();
    /**
    map_images阶段会进行类和分类的加载;
    loadImages阶段会调用load方法
    */
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
    didCallDyldNotifyRegister = **true**;
#endif
}

项目中用自定义的链表结构管理消费者(已经作为对象,加了一层封装),链表Node节点中会持有消费者的id标识,其中也涉及到消费者优先级的管理以及链表的添加、移除、节点保存等;

2,创建路由类检测协议遵守者并判断是否实现协议制定的方法,建立Map集合以满足条件的类为value,以类有关的业务type为key。(此业务type表示将要跳转的场景,此处是枚举概念)。

第一步找到所有注册的类

*Talking is cheap,show the codes*
/** 
 * Creates and returns a list of pointers to all registered class definitions.
 * @param outCount An integer pointer used to store the number of classes returned by
 *  this function in the list. It can be \c nil.
 * @return A nil terminated array of classes. It must be freed with \c free().
 * @see objc_getClassList
 */

OBJC_EXPORT Class _Nonnull * _Nullable
objc_copyClassList(unsigned int * _Nullable outCount)

第二步:判断类是否遵守指定的协议

/** 

 * Returns a Boolean value that indicates whether a class conforms to a given protocol.
 * @param cls The class you want to inspect.
 * @param protocol A protocol.
 * @return YES if cls conforms to protocol, otherwise NO.
 * @note You should usually use NSObject's conformsToProtocol: method instead of this function.
 */
OBJC_EXPORT BOOL
class_conformsToProtocol(Class **_Nullable** cls, Protocol * **_Nullable** protocol)

第三步:判断类是否实现了指定协议的方法

OBJC_EXPORT Method _Nullable
class_getClassMethod(Class **_Nullable** cls, **SEL** **_Nonnull** name)

3,路由类PushManager触发Event,并携带业务Map信息(包含type值:数字)。map信息被解析成真正的业务Event,然后被传递给总线BusSystem,之后便等待被唤醒触发业务事件。此间还涉及同步、异步、Timer事件的分发识别的逻辑,因和主题无关不赘述。

ps:此场景有结合runloop+线程保活技术轮询,建立BusSystem总线观念,功能分发。其功能相当于定时从起点接送乘客,不定站点下车。

  • 第四种:通过自定义section端的方式动态注册类与协议,形成映射。

此处重点解析BeeHive,同时会链接到多线程异步实现的源码。 (在模块入口类实现中 使用 BH_EXPORT_MODULE () 宏声明该类为模块入口实现类。) BeeHive还存在一种静态写入:通过plist方法直接实现,注册符合 BHModuleProtocol 协议模块类。

动态注册过程原理简述

(一)、基于类与协议的注册演示: @BeeHiveService(HomeServiceProtocol,BHViewController)

#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))

#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";

#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";

(二)、基于事件与类实例对象的关联注册演示:BH_EXPORT_MODULE(YES)官方文档解释:如果此参数设置为YES时,则会在启动之后第一屏内容展现之前异步执行模块的初始化,可以优化启动时时间消耗。

#define BH_EXPORT_MODULE(isAsync) \
+ (void)load { [BeeHive registerDynamicModule:[self class]]; } \
-(BOOL)async { return [[NSString stringWithUTF8String:#isAsync] boolValue];}

(三)、关键实现源码展示

__attribute__ ((constructor))
void initProphet() {
    /**
    The following functions allow you to install callbacks which will be called   

 * by dyld whenever an image is loaded or unloaded.  During a call to _dyld_register_func_for_add_image()

 * the callback func is called for every existing image.  Later, it is called as each new image

 * is loaded and bound (but initializers not yet run).
    */ 大意是在dyld安装或卸载镜像时,下面的函数设置的回调会被调用。在调用此函数期间,回调会被每一个已经存在的镜像文件调用。此时初始化程序还未运行完,
    _dyld_register_func_for_add_image(dyld_callback);

}
static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide) {
NSArray *mods = BHReadConfiguration(BeehiveModSectName, mhp);
for (NSString *modName in mods) {
    //code... =》 转化类

    // 此方法内部主要是事件与类(涉及到创建实例对象)的关联事件,
    [[BHModuleManager sharedManager] registerDynamicModule:cls];
}

//register services
NSArray<NSString *> *services = BHReadConfiguration(BeehiveServiceSectName,mhp);
for (NSString *map in services) {
  //code...=》map键值对解析成独立元素

     //方法内部:协议与类的分别转化成key-value,并加入字典self.allServicesDict。
    [[BHServiceManager sharedManager] registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
}

读取MachO文件的指定section段名下的信息,并进行解析成字符串。

    NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp)

{
    NSMutableArray *configs = [NSMutableArray array];
    unsigned long size = 0;
    const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
    
    uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size);

    unsigned long counter = size/sizeof(void*);
    for(int idx = 0; idx < counter; ++idx){
        char *string = (char*)memory[idx];
        NSString *str = [NSString stringWithUTF8String:string];
        if(!str)continue;
        BHLog(@"config = %@", str);
        if(str) [configs addObject:str];
    }
    return configs;
}

读取过程展示:

BeeHive动态注入简述.jpg

BeeHiveDemo build之后的macho文件查看如下:

macho.jpg

(四)关联苹果官方源码:在深入了解BeeHive源码后,意识到苹果官方源码中在对线程的内部实现也使用了注册section段的技术。

void dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
    codes...
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu, dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
    codes...
    return dx_push(dqu._dq, dc, qos);
}

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
//搜索dq_push 
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_main, lane,
    .do_type        = DISPATCH_QUEUE_MAIN_TYPE,
    .do_dispose     = _dispatch_lane_dispose,
    .do_debug       = _dispatch_queue_debug,
    .do_invoke      = _dispatch_lane_invoke,
    .dq_activate    = _dispatch_queue_no_activate,
    .dq_wakeup      = _dispatch_main_queue_wakeup,
     ///句柄
    .dq_push        = _dispatch_main_queue_push, 
);

#define DISPATCH_VTABLE_SUBCLASS_INSTANCE(name, ctype, ...) \
OS_OBJECT_VTABLE_SUBCLASS_INSTANCE(dispatch_##name, dispatch_##ctype, \
_dispatch_xref_dispose, _dispatch_dispose, \
.do_kind = #name, __VA_ARGS__)

#if OS_OBJECT_HAVE_OBJC2
#define OS_OBJECT_VTABLE_SUBCLASS_INSTANCE(name, ctype, xdispose, dispose, ...) \

//此处定义在名为__objc_data的__DATA段
__attribute__((section("__DATA,__objc_data"), used)) \

const struct ctype##_extra_vtable_s \
OS_OBJECT_EXTRA_VTABLE_SYMBOL(name) = { __VA_ARGS__ }

codes...

#else

  • 结尾赘述:

1,Swift-defer关键字演示:

var  a = 0
func test() -> Int{
    a = 2
    defer {
        a = -1
    }
    return test1()
}

func test1() -> Int {
    a = 6
    return 2
}

2,OC中,分懒加载类和非懒加载类,实现了+load方法的为非懒加载。如果大量频繁的使用+load方法,会不会涉及时间优化,因其中也涉及到主类与分类的attached。

3,代码中__attribute__用于指定编译特性:包括:Function Attributes、Variable Attributes、Type Attributes。在这里明显是作为修饰变量的 variable attributes。unused表示变量未必会被使用,section用于指定变量所保存到的数据段,参数为数据段名。