Skip to content

Instantly share code, notes, and snippets.

@yimajo
Created May 3, 2022 14:07
Show Gist options
  • Save yimajo/82fda211cac00e00d7be08888e0dd9cc to your computer and use it in GitHub Desktop.
Save yimajo/82fda211cac00e00d7be08888e0dd9cc to your computer and use it in GitHub Desktop.
SwiftLeeのDIがとても良いので紹介したい
// https://www.avanderlee.com/swift/dependency-injection/
// MARK: - Example
struct DataController {
@Injected(\.networkProvider) var networkProvider: NetworkProviding
func performDataRequest() {
networkProvider.requestData()
}
}
// MARK: - Use Cases
var dataController = DataController()
// InjectedValuesで間接的にTestDoubleをDIする例。
// InjectedValuesのstatic subscript<T>(_ keyPath: WritableKeyPath<InjectedValues, T>) -> Tを利用してセットしている。
// テストコードなどではこれで共通のものは一気にDIできる。
// プロダクションコードでもDIする対象を実際のDBにするとか、メインのSchedulerにするとかもできる。
InjectedValues[\.networkProvider] = MockedNetworkProvider()
print(dataController.networkProvider) // prints: MockedNetworkProvider()
// もしDIされていてもプロパティがinternalな場合は直接Setter Injectionでそれが使えるので安心。
dataController.networkProvider = NetworkProvider()
print(dataController.networkProvider) // prints 'NetworkProvider' as we overwritten the property wrapper wrapped value
// Production用を実行
dataController.performDataRequest() // prints: Data requested using the 'NetworkProvider'
// MARK: - InjectionKey
/// DIする対象に対してのKeyを抽象化する。
/// TestDoubleなものに切り替えられればそれをstaticに保存しており、切り替えられたものを利用する。
/// 例えば次のような準拠構造になる。
/// InjectionKey
/// - NetworkProviderKey
/// - DBProviderKey
/// - SchedulerProviderKey
public protocol InjectionKey {
/// Valueの型。準拠する側で決めてくれよな!
associatedtype Value
/// このInjectionKeyは現在の唯一の値(Value型)をstaticに返す。
/// デフォルト値とも言えるが、書き換えられるのでcurrentValueとしているんだろうか(書き換えられたら元のデフォルトに戻せない)。
static var currentValue: Self.Value { get set }
}
// MARK: - InjectedValues
/// 注入された依存物のアクセスを抽象化する。
/// 基本的にはこれがkeyで管理されたグローバル変数のDictionaryのように振る舞うだけの話。
/// SwiftのpropertyWrapperアノテーションはgetterとsetterによってそれを隠蔽化できる。
struct InjectedValues {
/// This is only used as an accessor to the computed properties within extensions of `InjectedValues`.
/// staticなメソッドを理容師た場合にこのインスタンスを利用する。
private static var current = InjectedValues()
/// プロパティcurrentValueを持つKey型を引数にし、currentValueのgetter, setterとして振る舞いをさせる。
/// extension InjectedValuesのプロパティから利用する。
static subscript<K>(key: K.Type) -> K.Value where K : InjectionKey {
get { key.currentValue }
set { key.currentValue = newValue }
}
/// A static subscript accessor for updating and references dependencies directly.
/// [KeyPath]でアクセスする際にsetterとgetterが利用される。
/// つまりDIする際と、利用する際の2つ。
static subscript<T>(_ keyPath: WritableKeyPath<InjectedValues, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
// MARK: - NetworkProvider
/// NetworkProviderのために作る。
/// つまりDI対象の数に応じて作成する必要がある。
private struct NetworkProviderKey: InjectionKey {
/// 本来は省略可能だが説明と理解のためにわざわざ書くと、
/// InjectionKeyのValue型をNetworkProvidingとしている。
typealias Value = NetworkProviding
/// protocol側のcurentValueを定義する必要がある。
/// Valueに直接NetworkProvidingと書けば上のtypealiasなんて必要ないが、説明のため。
/// 初期値が設定できるのでプロダクション用の型にしておけばいいと思う。
/// もし初期値をセットしたくない(セットしてなかったらクラッシュしたい)などの場合、
/// これをOptionalにしInjectionKeyもOptionalにし、force unwrapで使えばいいのかもしれない。
static var currentValue: Value = NetworkProvider()
}
protocol NetworkProviding {
func requestData()
}
/// Production
struct NetworkProvider: NetworkProviding {
func requestData() {
print("Data requested using the `NetworkProvider`")
}
}
/// TestDouble
struct MockedNetworkProvider: NetworkProviding {
func requestData() {
print("Data requested using the `MockedNetworkProvider`")
}
}
// MARK: - Shortcut
/// extensionでInjectedValuesにプロパティを生やしてKeyPath利用をしやすくする。
/// これによってInjectedValuesで使えるKeyPathを増やしても探しやすくもできる。
extension InjectedValues {
var networkProvider: NetworkProviding {
get { Self[NetworkProviderKey.self] }
set { Self[NetworkProviderKey.self] = newValue }
}
}
// MARK: - Property Wrapper
@propertyWrapper
struct Injected<T> {
/// StaticなInjectedValuesにアクセスするためのkeyPathを定義する
private let keyPath: WritableKeyPath<InjectedValues, T>
/// @propertyWrapperの必須プロパティ。
/// StaticにInjectedValuesにアクセスする。
var wrappedValue: T {
get { InjectedValues[keyPath] }
set { InjectedValues[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedValues, T>) {
self.keyPath = keyPath
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment