
开发工具【免费下载链接】SwinjectDependency injection framework for Swift with iOS/macOS/Linux项目地址https://gitcode.com/gh_mirrors/sw/Swinject点击查看免费下载本篇技术指南围绕 Swinject 依赖注入框架中三种核心注入模式展开构造器注入Initializer Injection、属性注入Property Injection与方法注入Method Injection。Swinject 是面向 Swift 的轻量级 DI 框架支持 iOS / macOS / tvOS / watchOS / Linux阅读完本文后你将掌握在Container中注册服务并为依赖实例注入依赖的三种标准写法理解initCompleted回调的执行时机与源码原理并学会在依赖可选与依赖必需两种场景下做出正确的模式选择为理解循环依赖Circular Dependencies与对象作用域Object Scopes打下基础。一、三种注入模式概览如何选择在 Documentation/InjectionPatterns.md 中Swinject 将依赖注入的实现方式归纳为三种模式选择依据取决于被依赖实例在缺少依赖时能否正常工作模式注入载体适用场景依赖是否可选构造器注入Initializer Injection初始化器init参数被依赖实例离开该依赖就无法工作必需属性注入Property Injection可写属性setter property该依赖对实例而言是可选的可选方法注入Method Injection方法参数与属性注入类似但通过方法传递可选三者共同的前提是依赖的实际类型由 Swinject 在创建实例时自动解析而不是由调用方手工指定。下面分别用仓库测试中真实使用的Animal/Cat/Person/PetOwner体系见 Tests/SwinjectTests/Person.swift来演示。二、构造器注入Initializer Injection2.1 定义方式构造器注入通过在**注册闭包factory closure**内调用r.resolve(Animal.self)并将结果传入目标类型的初始化器实现let container Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { r in PetOwner(pet: r.resolve(Animal.self)!) }其中协议与类的定义如下protocol Animal { func sound() - String } class Cat: Animal { init() { } func sound() - String { return Meow } } protocol Person { } class PetOwner: Person { let pet: Animal init(pet: Animal) { self.pet pet } }注意传给PetOwner初始化器的Animal的真实类型此处是Cat完全由 Swinject 在创建PetOwner实例时自动解析注册闭包中无需也不应关心具体类型。这与 Documentation/DIContainer.md 中介绍的Service / Component / Factory / Container四术语体系一致Animal是服务协议Cat是组件实现注册闭包{ r in ... }即工厂。2.2 源码印证register 与 resolve 的调用链从 Sources/Container.swift 的源码看register(_:name:factory:)最终调用_register将工厂闭包与ServiceKey由服务类型、注册名、参数类型共同构成一并存入容器的ThreadSafeDictionary当调用resolve时Sources/Container.swift 中的resolve(entry:invoker:)会incrementResolutionDepth()递增解析深度创建当前对象图标识GraphIdentifier调用工厂闭包invoker(entry.factory)即执行PetOwner(pet: r.resolve(Animal.self)!)将创建出的实例写入entry.storage实例存储与对象作用域相关返回解析出的实例。测试 Tests/SwinjectTests/ContainerTests.swift 中的testContainerAcceptsInitializerInjection验证了该路径注册Cat与PetOwner(pet:)后container.resolve(Person.self)得到的owner?.pet非空。2.3 使用建议当依赖是运行必需例如PetOwner没有宠物就无法成立时优先使用构造器注入依赖关系通过初始化器签名显式暴露实例在构造完成后即为完整可用状态fully initialized。r.resolve(Animal.self)!中的强制解包意味着若该类型未注册resolve返回nil!将导致运行时崩溃。生产代码中更稳妥的做法是使用可选绑定或通过debugHelper的失败日志定位缺失注册参见 Sources/DebugHelper.swift 中resolutionFailed的处理。三、属性注入Property Injection3.1 适用场景属性注入通过 setter 属性传递依赖适用于依赖对实例而言是可选的场景——实例即使没有该属性也能工作。以PetOwner2为例let container Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { r in let owner PetOwner2() owner.pet r.resolve(Animal.self) return owner }其中class PetOwner2: Person { var pet: Animal? init() { } }注意这里pet声明为可选类型Animal?r.resolve(Animal.self)也无需强制解包体现依赖可选的语义。3.2 使用 initCompleted 回调进行属性注入除了在注册闭包内完成注入还可以使用initCompleted回调原文称 initialization callback把实例创建与依赖注入两阶段分离let container Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { _ in PetOwner2() } .initCompleted { r, p in let owner p as! PetOwner2 owner.pet r.resolve(Animal.self) }两种写法最终效果相同但initCompleted更适合以下场景依赖注入逻辑较长希望保持注册闭包简洁需要解决循环依赖详见 Documentation/CircularDependencies.md需要为同一个服务挂载多个注入回调。3.3 源码印证initCompleted 的执行时机从 Sources/ServiceEntry.swift 源码可见initCompleted(_:)将回调追加到initCompletedActions数组public func initCompleted(_ completed: escaping (Resolver, Service) - Void) - Self可链式调用返回selfContainer在工厂闭包执行完成之后、返回实例之前通过 Sources/Container.swift 中的entry.initCompleted触发这些回调if let completed entry.initCompleted as? (Resolver, Any) - Void, let resolvedInstance resolvedInstance as? Service { completed(self, resolvedInstance) }也就是说initCompleted中的闭包总能拿到已完全构造好的实例p再通过p as! PetOwner2类型转换后填充其属性。测试 Tests/SwinjectTests/ContainerTests.swift 的testContainerAcceptsPropertyInjectionInInitCompletedEvent与testContainerRaisesInitCompletedEventWhenNewInstanceIsCreated分别验证了注入成功与回调确实被触发两个事实同一文件中还有testContainerRaisesInitCompletedEventForAllSubscribedClosuresWhenNewInstanceIsCreated证明多次调用initCompleted时每个回调都会被依次执行。关键提示回调中的p as! PetOwner2使用了强制类型转换务必保证闭包所挂载的服务类型与转换目标一致否则会触发运行时错误。官方推荐先as?安全转换再使用。四、方法注入Method Injection4.1 定义方式方法注入与属性注入思路类似但依赖通过方法而非直接给属性赋值传入。以PetOwner3为例let container Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { r in let owner PetOwner3() owner.setPet(r.resolve(Animal.self)!) return owner }其中class PetOwner3: Person { var pet: Animal? init() { } func setPet(pet: Animal) { self.pet pet } }同样地也可以改用initCompleted回调let container Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { r in let owner PetOwner3() owner.setPet(r.resolve(Animal.self)!) return owner }let container Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { _ in PetOwner3() } .initCompleted { r, p in let owner p as! PetOwner3 owner.setPet(r.resolve(Animal.self)!) }注意此处setPet的入参是非可选的Animal因此注入时仍需r.resolve(Animal.self)!强制解包方法内部再把依赖存入属性。4.2 与属性注入的取舍方法注入的优势在于方法体内可以对传入的依赖做校验、转换或延迟赋值并允许一次方法调用同时设置多个依赖其劣势与属性注入相同——实例在构造完成后存在一个依赖尚未就绪的中间状态调用方若过早使用实例可能拿到nil。因此二者都适用于可选依赖而必需依赖请优先选择构造器注入。测试 Tests/SwinjectTests/ContainerTests.swift 中testContainerAcceptsMethodInjectionInRegistration与testContainerAcceptsMethodInjectionInInitCompletedEvent分别覆盖了注册闭包内注入与initCompleted 回调内注入两条路径。仓库测试中的PetOwnerTests/SwinjectTests/Person.swift同时提供了init(pet:)、可变属性pet与injectAnimal(_:)方法正是三种模式共存的真实示例。五、三种模式之外的进阶要点5.1 initCompleted 与循环依赖当两个实例互相依赖时若都走构造器注入会陷入无限递归。Swinject 在解析深度超过阈值时直接终止从 Sources/Container.swift 可见maxResolutionDepth为 200超限会触发fatalError(Infinite recursive call for circular dependency has been detected. To avoid the infinite call, initCompleted handler should be used to inject circular dependency.)。因此官方规定循环依赖中至少一方必须通过属性/方法注入且须放在initCompleted回调中完成具体方案见 Documentation/CircularDependencies.md。5.2 命名注册与运行时参数同一服务类型需要多个实现时可用register(_:name:factory:)命名注册resolve(_:name:)按名解析注册闭包还可声明运行时参数如{ _, name in ... }解析时用resolve(_:argument:)单参数或resolve(_:arguments:,_:)多参数传入详见 Documentation/DIContainer.md。5.3 对象作用域的影响无论采用哪种注入模式被注入实例的共享策略都由对象作用域决定默认.graph作用域下同一对象图内多次解析同一服务会返回同一实例.transient每次解析都是新实例.container则在容器生命周期内共享。注入模式决定怎么传作用域决定传的是不是同一个两者正交。可进一步阅读 Documentation/ObjectScopes.md。六、小结维度构造器注入属性注入方法注入依赖必需性必需可选可选注入位置注册闭包内构造参数注册闭包或initCompleted中赋值属性注册闭包或initCompleted中调用方法实例完整性构造完成即完整存在中间空窗期存在中间空窗期循环依赖支持否单靠它会导致无限递归是配合initCompleted是配合initCompleted掌握这三种注入模式后即可为后续学习循环依赖解决Documentation/CircularDependencies.md、容器层级Documentation/ContainerHierarchy.md与Assembly 模块化注册Documentation/Assembler.md打下基础。更多文档入口见 Documentation/README.md。赞分享开发工具【免费下载链接】SwinjectDependency injection framework for Swift with iOS/macOS/Linux项目地址https://gitcode.com/gh_mirrors/sw/Swinject点击查看免费下载相关推荐Swinject 循环依赖Circular Dependencies完全指南用 initCompleted 打破注入死锁Swinject 循环依赖Circular Dependencies完全指南用 initCompleted 打破注入死锁 循环依赖是指两个或更多实例互相依开发工具DesignPatternsPHP 依赖注入Dependency Injection模式详解PHP 8 构造函数注入实战DesignPatternsPHP 依赖注入Dependency Injection模式详解PHP 8 构造函数注入实战 依赖注入Dependency示例工程教程Swinject实战iOS应用中的依赖注入模式Swinject实战iOS应用中的依赖注入模式 本文深入探讨了在iOS应用中使用Swinject实现依赖注入的多种模式和实践方法。涵盖了ViewControl开发工具上一篇Windows SSL证书自动化终极指南使用win-acme轻松管理Lets Encrypt证书下一篇degit的JavaScript API在Node.js脚本中集成项目脚手架功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考