ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Swinject 延迟注入实战指南:Lazy 与 Provider 的原理、用法与对象作用域交互

Swinject 延迟注入实战指南:Lazy 与 Provider 的原理、用法与对象作用域交互 开发工具【免费下载链接】SwinjectDependency injection framework for Swift with iOS/macOS/Linux项目地址https://gitcode.com/gh_mirrors/sw/Swinject点击查看免费下载导读依赖注入框架通常要求在解析对象时立即构建完整的依赖树但某些依赖成本高昂、需要按需创建或带有动态语义此时延迟注入Delayed Injection是最优解。Swinject 通过LazyType与ProviderType两种包装器wrapper提供延迟注入能力前者模拟 Swift 的lazy变量语义首次访问创建、后续复用后者模拟计算属性语义每次访问都创建新实例。读完本文你将掌握两种延迟注入的声明方式、底层解析原理、与对象作用域ObjectScope的交互行为以及它们在循环依赖与类型转发场景下的实战用法。为什么需要延迟注入在 Swinject 的Container中常规注册的解析流程是container.resolve(...)时立即执行注册闭包factory将所有依赖一次性构建完毕。但在以下场景中你并不希望依赖在解析时就立刻被创建依赖的初始化开销大网络连接、数据库会话、昂贵对象希望用到时才创建依赖本身带有可变状态需要每次访问都拿到新实例的语义对象之间存在循环引用需要借助延迟注入打破构建死循环依赖的真实类型或创建时机由运行时决定。官方文档 Documentation/DelayedInjection.md 明确给出两条核心语义Lazy行为类似 Swift 的lazy变量——首次访问时创建并缓存同一个值供后续所有访问使用Provider行为类似 Swift 的计算属性——每次访问都创建一个新实例。一个关键便利点LazyType与ProviderType都无需显式注册只要容器中存在Type本身的注册解析LazyType/ProviderType就会自动生效。三种注入方式的对比实验要理解直接注入、Lazy与Provider的区别最直观的方式是看同一个注册闭包在三种注入方式下的执行时机与执行次数。先注册一个带有副作用的Int服务var value 0; container.register(Int.self) { _ in value 1; print(creating Int) return value; }注册闭包每执行一次value就自增一次因此输出的数字可以精确反映该依赖到底被创建了几次。直接注入Direct Injectionstruct DirectCounter { let integer: Int func print() { print(printing) print(integer) print(integer) print(integer) } } container.register(DirectCounter.self) { DirectCounter(integer: $0.resolve(Int.self)!) } let counter container.resolve(DirectCounter.self)! counter.print()输出为creating Int printing 1 1 1creating Int出现在printing之前说明依赖在resolve(DirectCounter.self)时就被立即创建了integer被保存为值类型属性三次打印均为同一个1。Lazy 注入struct LazyCounter { let integer: LazyInt func print() { print(printing) print(integer) print(integer) print(integer) } } container.register(LazyCounter.self) { LazyCounter(integer: $0.resolve(LazyInt.self)!) } let counter container.resolve(LazyCounter.self)! counter.print()输出为printing creating Int 1 1 1creating Int第一次出现在printing之后——解析LazyCounter时并未创建Int直到integer属性被访问时才触发创建且创建恰好发生一次三次访问得到同一个1与 Swiftlazy变量的首访创建、之后复用语义完全一致。Provider 注入struct ProviderCounter { let integer: ProviderInt func print() { print(printing) print(integer) print(integer) print(integer) } } container.register(ProviderCounter.self) { ProviderCounter(integer: $0.resolve(ProviderInt.self)!) } let counter container.resolve(ProviderCounter.self)! counter.print()输出为printing creating Int 1 creating Int 2 creating Int 3每次访问integer属性都重新执行一次注册闭包三次访问共创建三次分别得到1、2、3与 Swift 计算属性的每次求值语义完全一致。源码级原理InstanceWrapper 协议与解析回退延迟注入并非通过特殊注册实现而是Container在常规解析失败后自动回退到包装器解析wrapper resolution路径。其底层载体是 Sources/InstanceWrapper.swift 中定义的协议protocol InstanceWrapper { static var wrappedType: Any.Type { get } init?(inContainer container: Container, withInstanceFactory factory: ((GraphIdentifier?) - Any?)?) }LazyService、ProviderService以及Optional都实现了该协议。Container的解析逻辑Sources/Container.swift是先用Service.self构造ServiceKey查注册表若找不到条目则调用resolveAsWrapper尝试把请求类型当作InstanceWrapper处理if let entry getEntry(for: key) { resolvedInstance resolve(entry: entry, invoker: invoker) } if resolvedInstance nil { resolvedInstance resolveAsWrapper(name: name, option: option, invoker: invoker) }resolveAsWrapper的关键逻辑是将包装器的wrappedType即Lazy/Provider的泛型参数Service作为真正的服务类型去注册表查找条目找到后用闭包捕获该条目构造一个延迟执行的 factory再交给包装器持有。也就是说LazyInt能否解析成功取决于容器里有没有Int的注册——这也是Lazy/Provider无需显式注册的原因。Lazy 的缓存实现在 Sources/InstanceWrapper.swift 中LazyService的成员如下_instance: Service?缓存槽位首次访问时通过factory(graphIdentifier)创建并赋值之后所有访问直接返回缓存值graphIdentifier与weak var containerLazy在创建时记录当前对象图标识currentObjectGraph并在延迟解析时通过restoreObjectGraph恢复该对象图从而保证首次访问时返回的实例与当时解析上下文一致。instance的 getter 严格实现了只创建一次public var instance: Service { if let instance _instance { return instance } else { _instance makeInstance() return _instance! } }Provider 的每次新建实现Sources/InstanceWrapper.swift 中的ProviderService不持有任何缓存instancegetter 每次都直接调用 factorypublic var instance: Service { return factory(.none) as! Service }注意Provider传入factory(.none)不恢复对象图因此每次访问都是独立的新一轮创建这使其与对象作用域的组合行为也不同于Lazy详见下文。与对象作用域ObjectScope的交互Lazy/Provider的延迟语义会与对象作用域叠加。测试代码 Tests/SwinjectTests/EmploymentAssembly.swift 注册了同时注入普通依赖、Lazy依赖与Provider依赖的Employer/Employee结构相关断言分别在 Tests/SwinjectTests/LazyTests.swift 与 Tests/SwinjectTests/ProviderTests.swift 中.transient每次解析新建Lazy与Provider每次都产生不同的新实例.graph对象图内复用Lazy在同一对象图内复用同一个实例断言通过而Provider依然每次产生不同实例.container容器级单例Lazy与Provider都返回同一个实例。其背后原因可从实现层面解释Lazy通过缓存 对象图恢复restoreObjectGraph天然继承了作用域语义而Provider每次访问都执行factory(.none)重新走一遍解析只遵循容器级作用域的全局单例约束。进阶用法参数、命名与类型转发Lazy/Provider在解析时同样支持常规解析的各种能力这在官方测试中均有覆盖LazyTests.swift、ProviderTests.swift带参数解析——为注册闭包传入构造参数container.register(Dog.self) { (_, name, _: Int) in Dog(name: name) } let lazy container.resolve(LazyDog.self, arguments: Hachi, 42) let provider container.resolve(ProviderDog.self, arguments: Hachi, 42)命名注册——name同样作用于包装器解析且必须精确匹配container.register(Dog.self, name: Hachi) { _ in Dog() } container.resolve(LazyDog.self, name: Hachi) // 成功 container.resolve(ProviderDog.self, name: Mimi) // 失败返回 nil类型转发——通过.implements注册的类型转发同样对包装器生效container.register(Dog.self) { _ in Dog() }.implements(Animal.self) container.resolve(LazyAnimal.self) // 非 nil container.resolve(ProviderAnimal.self) // 非 nil未注册基础类型——若Type本身没有任何注册解析LazyType/ProviderType会返回nilLazyTests.swift、ProviderTests.swift。循环依赖场景中的价值循环依赖是依赖注入中的经典难题。借助延迟注入Swinject 可以在不依赖initCompleted回调的情况下优雅地处理某些循环链LazyTests.swift 证明在.graph作用域下Employee.employer与Employer.lazyEmployee.instance.employer均指向同一个Employer实例且resolve(LazyEmployee.self)可以正常完成循环链的构建ProviderTests.swift 则证明通过ProviderEmployer解析得到的实例其普通依赖保持同一性employer.employee.employer employer而Provider包装的依赖每次访问都得到新实例。因此当两个对象互相引用且其中一方可以在运行时才解析时用Lazy/Provider包装其中一个方向即可打破构建死循环若循环无法通过延迟注入化解容器仍会在解析深度达到上限时触发fatalError提示使用initCompleted见 Sources/Container.swift。如何选择Lazy 还是 Provider结合官方文档语义与实际场景可以按以下标准决策需求选择依据昂贵的依赖只创建一次之后复用LazyType类似 Swiftlazy变量缓存首访结果每次访问都需要全新实例如会话、请求上下文ProviderType类似计算属性每次访问重新解析打破双向循环引用保持对象图内同一性LazyType保留graphIdentifier并恢复对象图需要工厂式按需生产、且不关心对象图复用ProviderType每次独立创建可选依赖Type?OptionalOptional同样实现了InstanceWrapper解析失败时得到nil结语Swinject 的延迟注入通过InstanceWrapper协议与容器解析回退机制将创建时机从解析阶段剥离出来交给业务侧按需触发。Lazy与Provider虽表面相似却分别对应缓存复用与每次新建两种截然不同的语义且与对象作用域、参数解析、命名注册、类型转发、循环依赖等特性深度联动。理解本文的对比实验与底层实现即可在真实项目中准确选用延迟注入避免昂贵对象被过早创建、或错误复用了本应每次新建的实例。建议继续阅读 Tests/SwinjectTests/LazyTests.swift 与 Tests/SwinjectTests/ProviderTests.swift 中的完整断言以及 Sources/InstanceWrapper.swift 与 Sources/Container.swift 的包装器解析实现以掌握全部边界行为。赞分享开发工具【免费下载链接】SwinjectDependency injection framework for Swift with iOS/macOS/Linux项目地址https://gitcode.com/gh_mirrors/sw/Swinject点击查看免费下载相关推荐深入解析 Spring Lazy 注解延迟初始化与延迟注入的源码级实践指南深入解析 Spring Lazy 注解延迟初始化与延迟注入的源码级实践指南 导读 Lazy 是 Spring 3.0 起引入的核心注解用于控制 Bean示例工程文档Angular Providers 实战指南掌握依赖注入中的 Provider 配置与作用域Angular Providers 实战指南掌握依赖注入中的 Provider 配置与作用域 导读 Provider提供者是 Angular 依赖注入D文档教程知识库EOSIO cleos system canceldelay 实战指南取消延迟交易的命令用法与链上实现原理EOSIO cleos system canceldelay 实战指南取消延迟交易的命令用法与链上实现原理 导读 cleos system canceldel区块链上一篇NgRx Effect Schematics 完全指南用 ng generate effect 快速生成并注册 Angular Effect下一篇如何用树莓派打造精准的近距离检测系统IoT-For-Beginners项目实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表