導言
注入(Injection)在任何語言裏都是非常有效的解耦利器。
請不要把上面的注入和注入攻擊混淆起來,不要把本貓逼得變身成黑客貓 😉
本篇就帶大家看看如何解決Swift中Injection的一個常見問題:
怎麼解決泛型協議實體兼容性問題???
快點射(Swift Injection), 別想歪
如果你沒有被上面那拗口一句 怎麼解決泛型協議實體兼容性問題???
嚇跑的話,那麼恭喜你,堅持看下去你會覺得其實也沒想象的那麼難…
上帝說要有協議,所以協議來了:
protocol Power{
func publisher() -> AnyPublisher<String,URLError>
}
什麼人有Power? 超人算一個吧:
struct Superman: Power{
func publisher() -> AnyPublisher<String, URLError> {
// 極其複雜的邏輯,你不用管... ;)
}
}
下面快在我們的Model中使用超人吧:
struct Model {
private var powerMan:Power
init(powerMan:Power = Superman()){
self.powerMan = powerMan
}
}
上面的powerMan就是一個注入(Injection)屬性。
注意,之所以powerMan類型是Power協議而不是一個實體類型(比如Superman),就是爲後面的注入鋪路。
注入一個方便的地方是測試:
struct MockPower: Power {
func publisher() -> AnyPublisher<String, URLError> {
Just("Mock Power")
.setFailureType(to: URLError.self)
.eraseToAnyPublisher()
}
}
現在我們可以在不改動Model的情況下,用一個MockPower來測試其邏輯:
let model = Model(powerMan: MockPower())
而不是正常邏輯中的:
let model = Model()
That’s Injection!!!
理想很骨感,現實很殘酷
好吧,上面只是理想世界中的情況.
現實中可沒那麼簡單!!!
回到Power協議:
protocol Power{
func publisher() -> AnyPublisher<String,URLError>
}
注意返回發佈者的錯誤類型,它指定了URLError!
這有問題麼? 有!
沒有我說毛線呢?
現實中遵從Power協議中錯誤類型,應該是由實體類型決定的!這意味着在這裏不能限定死錯誤類型!
所以我們要將Power類型做如下修改:
protocol Power{
associatedtype ErrorType:Error
func publisher() -> AnyPublisher<String,ErrorType>
}
同理需要如下修改Superman類型:
struct Superman: Power {
enum Error:Swift.Error{
case krypton // 超人怕氪星物質???
case lover // 超人擔心女友???
}
func publisher() -> AnyPublisher<String, Self.Error> {
// 複雜實現,不用你管... ;)
}
}
你會說:
儂看,實體類型自定義錯誤也很容易嘛…
NO,NO… 你高興的太早了,問題出在ViewModel裏:
struct Model{
private var powerMan: Power
}
Protocol ‘Power’ can only be used as a generic constraint because it has Self or associated type requirements
看到上面錯誤提示了嗎???
協議含有一個泛型(這裏叫associated type),這樣的協議類型是不能直接作爲generic constraint的哦,如上代碼所示。
這樣一來,無法完成Injection了…
沒有辦法了麼?非也非也…
以其人之道,還治其人之身
泛型惹的禍,我們還靠泛型來救!
將ViewModel定義改成如下形式:
struct Model<S:Power>{
private var powerMan: S
}
錯誤沒有了,我們又可以愉快地繼續了…
不過危機還沒有完全解除,爲了完成初始化Injection,我們還需要修改初始化器的代碼:
struct Model<S:Power>{
private var powerMan: S
init(powerMan:S = Superman() as! S){
self.powerMan = powerMan
}
}
在設置初始化器的默認參數時,我們需要做一個類型強轉.否則編譯器還是得給你難堪。
不過最艱難的時刻已經過去了,我們現在又可以愉快地寫Injection測試了:
struct MockPower: Power {
func publisher() -> AnyPublisher<String, Error> {
Just("Mock")
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
}
// 正經的Model
let model = Model<Superman>()
// 測試的Model
let testModel = Model(powerMan: MockPower())
這波操作穩中帶皮,可以!
雷打不動的結語
照例還是要寫總結…
Injection在大多數現代編程語言中都是不可忽視的重要模式,尤其是動態語言中,雖然Swift不能算是動態語言…
Swift到了5.1版本後,API已基本固定,但其中語法語義還是有可以優化的地方,讓我們翹首以盼吧 😉
相信豬貓,沒錯噠…