快點射!(Swift Injection) 不帶這樣的!

導言

注入(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已基本固定,但其中語法語義還是有可以優化的地方,讓我們翹首以盼吧 😉

相信豬貓,沒錯噠…
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章