Firebolt 0.5.2

Firebolt 0.5.2

Andrew Aquino维护。



Firebolt 0.5.2

  • 作者
  • drewkiino

Firebolt

Firebolt 是一个为 Swift 编写的依赖注入框架。受 KotlinKoin 启发。该框架轻量级且不偏不倚,凭借函数式编程的解决方案简单实现。

贡献

Firebolt 是一个开源项目,如果您想为其贡献力量,请随时联系我。您也可以发起一个 pull-request 或开放 issues。

安装

Cocoapods

Firebolt 通过 CocoaPods 提供。要安装它,只需在 Podfile 中添加以下行即可

pod 'Firebolt'

Swift 包管理器

https://github.com/DrewKiino/Firebolt.git

文档

用法

  1. 实例化 Resolver
let resolver = Resolver()
  1. 注册依赖。
class ClassA {}

resolver.register { ClassA() }
  1. 使用 get() 限定符解决内部依赖。
class ClassA {}
class ClassB { init(classA: ClassA) }
 
resolver
    .register { ClassA() }
    .register { ClassB(classA: $0.get()) } // <-- get() qualifier
  1. 使用 get() 关键字开始使用依赖注入进行编码。
let classA: ClassA = resolver.get()
let classB: ClassB = resolver.get()

// Or if you don't care about having more than one system
// you can access the global scope.
let classA: ClassA: = get()

作用域

你可以在注册期间传递一个 scope 限定符,告诉 Resolver 如何解决实例。

当前支持的 scope 表达形式是:

enum Scope {
    case single // <- the same instance is resolved each time
    case factory // <- unique instances are resolved each time
}

你可以这样设置作用域。 .single 是默认作用域设置。

resolver.register(.single) { ClassA() } /// only a single instance will be created and shared when resolved
resolver.register(.factory) { ClassA() }  /// multiple instances are created each time when resolved
resolver.register { ClassA() } /// .single is the default

// now these two are of the same instances 
let classA: ClassA = resolver.get() 
let classA: ClassA = resolver.get()

单例解决也适用于具体类的协议。

resolver.register(.single, expect: ClassAProtocol.self) { ClassA() }

let classA1: ClassAProtocol = resolver.get()
let classA2: ClassAProtocol = resolver.get()

// Both ClassA1 and ClassA2 are resolved from the same concrete instance

参数

你可以在注册期间按如下方式传递参数。

let resolver = Resolver()
let environment: String = "stage"

reasolver.register { ClassD(environment: environment, classA: $0.get()) }

如果需要在调用位置传递参数,你可以在注册期间指定期望的类型。

resolver.register(arg1: String.self) { ClassD(environment: $0) }

然后你可以传递参数。

let classD: ClassD = resolver.get("stage")

您也可以传递多个参数。

resolver.register(arg1: String.self, arg2: Int.self) { ClassD(environment: $0, timestamp: $1) }

let classD: ClassD = resolver.get("stage", 1200)

您还可以这样传递可选类型。

class ClassE { init(value: String?) {} }

let resolver = Resolver()
resolver.register(arg1: String?.self) { ClassE($0) }

// no arguments tells the resolver to pass nil instead
let classE: ClassE = resolver.get() 
let classE: ClassE = resolver.get("SOME_VALUE")

对于依赖于依赖之间的共享非注册参数,您可以在register块内部使用上游参数本身传递参数。

let resolver = Resolver()

class ClassC {
    init(classA: ClassA, classB: ClassB) {}
}

resolver
    .register(arg1: ClassA.self) { ClassB(classA: $0) }
    .register(arg1: ClassA.self) { 
        // ClassA is now shared between ClassB and ClassC
        // without registration
        ClassC(classA: $0, classB: $0.get($0)) 
    }

// Then call them like so
let classA: ClassA = ClassA()
let classC: ClassC = get(classA)

协议兼容性

协议兼容性也由Resolver支持。假设您想注册一个ClassA协议和一个ClassAImpl具体类型,您可以使用expect参数。

protocol ClassA { func foo() }

class ClassAImpl: ClassA { func foo() {} }

let resolver = Resolver()

resolver.register(expect: ClassA.self) { ClassAImpl() }

然后在调用点调用它。

let classA: ClassA = get() // <- ClassAImpl will be returned

您还可以为相同的具体类型提供多个协议支持。

protocol ClassAVariantA { func foo() }
protocol ClassAVariantB { func bar() }

class ClassA: ClassAVariantA, ClassAVariantB { 
    func foo() {} 
    func bar() {}
}

let resolver = Resolver()

resolver.register { ClassA() }

// multiple resolutions using the same concrete type with the expect qualifier
let variantA: ClassAVaraintA = get(expect: ClassA.self)
let variantB: ClassAVaraintB = get(expect: ClassA.self)

或者使用不同的方法,为相同的具体类传递多个expect。

let resolver = Resolver()

resolver.register(expects: [ClassAVaraintA.self, ClassAVaraintB.self]) { ClassA() }

let classA: ClassAVaraintA? = get()
let classA: ClassAVaraintB? = get()

如果有依赖项需要协议兼容性,但您只支持具体类,可以这样做

class ClassA: ClassAVariantA {}

class ClassB { init(classAVariant: ClassAVariantA) {} }

let resolver = Resolver()

resolver
    .register { ClassA() }
    .register { ClassB(classAVariant: get(expect: ClassA.self)) }

// works
let classB: ClassB = get()

这是因为ClassA已经在依赖范围内注册,但我们可以使用get()限定符和调用点中传入的expect参数将其转换为预期的类型ClassAVaraintA

透明兼容性

使用some关键字,具有关联类型的协议可以被泛化。

考虑以下示例

protocol OpaqueProtocol {
  associatedtype Value
  func getValue() -> Value
}

class OpaqueClassA: OpaqueProtocol {
  func getValue() -> String { "hello" }
}

class OpaqueClassAB: OpaqueProtocol {
  init(classA: OpaqueClassA) {}
  func getValue() -> Int { 1 }
}

通过Firebolt,您能够解析透明类型。

let resolver = Resolver()
    .register(.single) { OpaqueClassA() }
    .register(.factory) { OpaqueClassB(classA: $0.get()) }

// this will work
let someClassA: some OpaqueProtocol = resolver.get(expect: OpaqueClassA.self)
let someClassB: some OpaqueProtocol = resolver.get(expect: OpaqueClassB.self)

// this will also work
let classA: OpaqueClassA = resolver.get()
let classB: OpaqueClassB = resolver.get()

// will print `true`
print(someClassA == classA)

线程安全

Firebolt有一个内部全局队列,确保依赖和解析器按相同的顺序注册/注销。

全局解析器

通常,当您初始化Resolver时,您可以可选地传递一个resolverIdUUID().uuidString会为您自动生成,这确保了在该解析器中注册的所有依赖项都对该解析器的实例独特,它们 nunca 可以与其他解析器共享。

如果您需要一个全局范围内的解析器,有一个特殊的解析器位于全局作用域,您可以通过使用Resolver类的global静态属性来访问它。

let resolver = Resolver.global // <-- resolves the GlobalResolver

resolver.register { ClassA() }

然后您可以在不指定解析器标识符的情况下全局注入依赖项。

// property scoped in another instance of the application 
// will resolve automatically for you.
let classA: ClassA = get()

Mock 解析器

还有一种特殊的 Resolver 子类,叫做 MockResolver,它是一个方便的类,用于创建小型项目范围的依赖图。

let mockResolver = MockResolver { resolver in
  resolver.register { ClassA() }
  resolver.register { ClassB(classA: resolver.get()) }
}

多个解析器

如果您想将依赖项分开,可以实例化多个解析器,每个解析器都有自己的作用域。当您释放这些解析器时,关联的依赖项实例也将被释放。

当您初始化一个 Resolver 时,必须传入一个 resolverId,然后火光(Firebolt)将此解析器注册到一个缓存中。

  1. 使用唯一标识符实例化一个 Resolver
let resolver1 = Resolver("Resolver_1")
resolver1.register { ClassA() }

let resolver2 = Resolver("Resolver_2")
resolver2.register { ClassA() }

// make sure to resolve using the Resolver itself using lamba
resolver2.register { ClassB(classA: $0.get()) }
  1. 然后通过各自的解析器引用来注入。
// resolves to `nil` because Resolver_1 never registered ClassB
let classB: ClassB = resolver1.get()

// resolves to ClassB 
let classB: ClassB = resolver2.get()

以下是一个使用类似设计的 InterfaceResolver 的示例。

let resolver: Resolver
init(resolver: Resolver) { self.resolver = resolver }

func viewDidLoad() {
    let classB: ClassB = resolver.get()
}

未注册给解析器的对象不会被其他解析器共享。这包括注册为 .single 的对象,除非它们是由 GlobalResolver 本身注册的,在这种情况下,它们成为真正的 Singleton

如果您初始化两个具有相同标识符的解析器,它们将共享相同的依赖项缓存。

let resolverA = Resolver("SAME_IDENTIFIER")
resolverA.register { ClassA() }

let resolverB = Resolver("SAME_IDENTIFIER")

// This will successfully resolve since ResolverB shares the same 
// identifier as ResolverA - thus the same cache of dependencies.
let classA: ClassA = resolverB.get() 

将解析器作为子类

如果您需要创建自己的 Resolver 类型,例如 MyAppResolver,则解析器可被子类化。

您应该在子类中通过初始化器传递自己的 resolverId。如果不这样做,您的子类将继承为 GlobalResolver,因为一个不带标识符的独立 Resolver 类本质上会访问单例本身。

class MyAppResolver: Resolver {
    init() {
        super.init("MyAppResolver")
    }
}

let myResolver = MyAppResolver()
myResolver.register { ClassA() }

// this will work
let classA: ClassA = myResolver.get()

// this will also work
let classA: ClassA = get(resolverId: "MyAppResolver")

// this will fail because it is accessing the Global Resolver
let classA: ClassA = get()

注销依赖项

您可以通过以下方式注销依赖项。

resolver.register { ClassA() }

let classA: ClassA? = resolver.get() // will return ClassA

resolver.unregister(ClassA.self)

let classA: ClassA? = resolver.get() // will return nil

注销所有依赖项。

resolver
    .register { ClassA() }
    .register { ClassB() }

let classA: ClassA? = resolver.get() // will return ClassA
let classB: ClassB? = resolver.get() // will return ClassB

resolver.unregisterAllDependencies()

let classA: ClassA? = resolver.get() // will return nil
let classB: ClassB? = resolver.get() // will return nil

除了这些类型之外,注销所有依赖项。

resolver
    .register { ClassA() }
    .register { ClassB() }

let classA: ClassA? = resolver.get() // will return ClassA
let classB: ClassB? = resolver.get() // will return ClassB

resolver.unregisterAllDependencies(except: [ClassB.self])

let classA: ClassA? = resolver.get() // will return nil
let classB: ClassB? = resolver.get() // will return ClassB!

删除缓存的依赖关系

当通过 `.single` 范围创建依赖项时,它会被存储在其相应解析器的缓存中。

您可以通过以下方式删除该缓存。

resolver
    .register(.single) { ClassA() }

let classA1: ClassA? = resolver.get() 
let classA2: ClassA? = resolver.get() 

print(classA1.id == classA2.id) // will print true

resolver.dropCached([ClassA.self])

let classA3: ClassA? = resolver.get() 

print(classA1.id == classA3.id) // will print false

删除所有缓存的依赖项。

resolver
    .register(.single) { ClassA() }
    .register(.single) { ClassB() }

let classA1: ClassA? = resolver.get() 
let classA2: ClassA? = resolver.get() 

print(classA1.id == classA2.id) // will print true

let classB1: ClassB? = resolver.get() 
let classB2: ClassB? = resolver.get() 

print(classB1.id == classB2.id) // will print true

resolver.dropAllCachedDependencies()

let classA3: ClassA? = resolver.get() 
let classB4: ClassA? = resolver.get() 

print(classA1.id == classA3.id) // will print false
print(classB1.id == classB3.id) // will print false

或者删除所有但有例外类型。

resolver
    .register(.single) { ClassA() }
    .register(.single) { ClassB() }

let classA1: ClassA? = resolver.get() 
let classA2: ClassA? = resolver.get() 

print(classA1.id == classA2.id) // will print true

let classB1: ClassB? = resolver.get() 
let classB2: ClassB? = resolver.get() 

print(classB1.id == classB2.id) // will print true

resolver.dropAllCachedDependencies(except: [ClassB.self])

let classA3: ClassA? = resolver.get() 
let classB4: ClassA? = resolver.get() 

print(classA1.id == classA3.id) // will print false
print(classB1.id == classB3.id) // will print true

示例

Storyboard 解析

还可以使用 Firebolt 解析 storyboards。以下是一个示例:

// There are multiple ways to initialize a storyboard view code but in this case
// we will use a static initializer for the sake of allowing external parameters
class ViewController {
    class func initialize(userManager: UserManager): ViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        return storyboard.instantiateViewController(identifier: "ViewController") as! ViewController 
    }
}

// .. then register
resolver
  .register { UserManager() }
  .register { ViewController.initialize(userManager: $0.get()) }
  
// ... when resolving it
let vc: ViewController = resolver.get()

// ... or if you're using the Global Resolver
resolver.global
  .register { UserManager() }
  .register { ViewController.initialize(userManager: $0.get()) }

let vc: ViewController = get()

应用程序架构

// UserManager.swift
class UserManager {}

// ViewController.swift
class ViewController: UIViewController {
    public init(userManager: UserManager) {}
}

// AppDelegate.swift
class AppDelegate {

  let resolver = Resolver()

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    resolver.register { UserManager() }
    resolver.register { ViewController(userManager: $0.get()) }

    let viewController: ViewController = resolver.get()
    window?.rootViewController = viewController
  }
}

作者

Andrew Aquino, [email protected]

许可

Firebolt 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。