导言
注入(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已基本固定,但其中语法语义还是有可以优化的地方,让我们翘首以盼吧 😉
相信猪猫,没错哒…