Created
May 3, 2022 14:07
-
-
Save yimajo/82fda211cac00e00d7be08888e0dd9cc to your computer and use it in GitHub Desktop.
SwiftLeeのDIがとても良いので紹介したい
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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