Swift 4 泛型 - 一个泛型类,其变量符合具有关联类型的协议。
前言
很多时候,我发现我自己正在努力使用 Swift 的泛型协议和关联类型。
每次我想到一个功能的时候,在我脑海中会想到一件事:当我编码的时候,我会遇到非期望的一些奇怪错误和语法限制。
问题
上周,我团队里面的人问我关于这部分的问题,并且我们发现自己尝试了很多变通方法来解决这个问题。
他想要的是:一个泛型类,其变量符合具有关联类型的协议。
这可能听起来会很奇怪,所以在我们深入了解它后,你会明白的。
准备
准备一个 swift playground...
译者:playground 真的非常非常卡,建议新建 macOS -> CommandLine)
假设一种场景,我想要新建一个 protocol 作为某些数据的 provider。这个 protocol 有一个单一的方法来返回数据:
protocol DataProvider {
func giveData() -> Any
}
接下来,我想新建一个 class 作为数据的消费者:
class DataConsumer {
var data : Any?
var dataProvider : DataProvider?
func getData(){
self.data = self.dataProvider?.giveData()
}
}
现在,所有的事情看起来都 OK。
解决问题
但是 - 假如我想要它们倆(提供者&消费者)使用相同的数据类型怎么办? Swift 的惊人特性之一 - 泛型,听起来是个不错的方法。
声明 DataConsumer 具有符合某些协议的关联类型 T。然后在 DataConsumer 里面声明 dataProvider 变量的类型符合同一个关联类型 T。
这可能不错。想象一下,在某个外部作用域中初始化 DataConsumer 及其数据类型,然后在尝试赋值 dataProvider 时,自动完成地确认了在初始化 DataConsumer 时声明的类型。
让我们来试一试吧。
首先创建一个数据类型的 protocol:
protocol DataType {}
现在我们想要这个提供者和消费者使用相同的数据类型。
编辑我们的 DataConsumer 使其成为泛型类型。
//更新
class DataConsumer <T:DataType> {
//更新
var data : T?
var dataProvider : DataProvider?
func getData(){
self.data = self.dataProvider?.giveData()
}
}
现在,编译器会提示 giveData() 返回的是 Any 而不是 T。所以需要接着编辑 DataProvider :
protocol DataProvider {
//更新
associatedtype T : DataType
//更新
func giveData() -> T
}
然后再次返回到我们的 DataConsumer 去修改 dataProvider 变量,使其拥有像 DataConsumer 那样的关联类型:
class DataConsumer<T:DataType>{
var data : T?
//更新
var dataProvider : DataProvider<T>?
func getData(){
self.data = self.dataProvider?.giveData()
}
}
额…编译器现在又提示:
Cannot specialize non-generic type 'DataProvider'
我自己无法理解这个错误,所以不得不去网上搜索。
编译器针对这个错误提供了一个解决办法(Xcode 9):
Replace '<T>' with "
首先,双引号是什么意思?这是个奇怪的错误。
其次,点击 fix 会变成下面这样:
var dataProvider : DataProvider?
现在,编译器会抱怨其它的事情了:
Protocol 'DataProvider' can only be used as a generic constraint because it has Self or associated type requirements
额,假如这样的话,让我们试试其它的办法。不要向协议声明关联类型,而是让函数具有泛型参数并删除关联类型声明:
protocol DataProvider{
//更新
func giveData<T:DataType>() -> T
}
好像编译器现在没有抱怨了。
让我们测试一下我们现在写的这些吧...
创建一个 JSON 的数据类型和提供者:
struct JSONData : DataType {}
final class JSONDataProvider : DataProvider{
func giveData<T>() -> T {
return JSONData()
}
}
编译器现在又开始抱怨:
Cannot convert return expression of type 'JSONData' to return type 'T'
为啥?我们明明已经在 DataProvider 里面写明了 T 就是 DataType,并且我们的 JSONData 结构体就是符合 DataType 的...?
好吧,我们可以使用 Xcode 提供的方法:
struct JSONData : DataType {}
final class JSONDataProvider : DataProvider{
func giveData<T>() -> T {
//更新
return JSONData() as! T
}
}
丑爆了,但是可能好用...
在我们继续之前,加入这个扩展方法用于帮助我们鉴定我们的 DataType 类型:
extension DataType {
func whoAmI(){
print(type(of: self))
}
}
现在,在 JSONDataProvider 后面加入这些测试代码:
let dataConsumer = DataConsumer<JSONData>()
dataConsumer.dataProvider = JSONDataProvider()
dataConsumer.getData()
dataConsumer.data?.whoAmI()
哇噢!控制台输出:
JSONData
还有一个问题
好吧,我们还有一个问题。在 whoAmI 方法后面加入下面行代码:
dataConsumer.dataProvider = XMLDataProvider()
dataConsumer.getData()
编译虽然成功,但是现在调用 getData() 的时候会崩溃。你也可以尝试一下。
这是由于我们无法强制使用特定的 DataProvider 类型(还记吗: "Cannot specialize non-generic type 'DataProvider'")。
这个解决方案,除了没有给予我们想要的以外,还使我们的代码可读性很低。
在花了一些时间在这个问题上后。我终于忙完了别的事情。上面的解决方案虽然可以使用,但并不是我想要的。
protocol DataType {}
protocol DataProvider{
associatedtype T : DataType
func giveData() -> T
}
extension DataType{
func whoAmI() {
print(type(of: self))
}
}
class DataConsumer<T:DataProvider>{
var data : T.T?
var dataProvider : T?
func getData(){
self.data = self.dataProvider?.giveData()
}
}
struct JSONData : DataType {}
struct XMLData : DataType {}
class JSONProvider : DataProvider{
typealias T = JSONData
func giveData() -> JSONData {
return JSONData()
}
}
class XMLProvider : DataProvider{
typealias T = XMLData
func giveData() -> XMLData {
return XMLData()
}
}
let consumer1 = DataConsumer<JSONProvider>()
consumer1.dataProvider = JSONProvider()
consumer1.getData()
consumer1.data?.whoAmI()
//Error
//consumer1.dataProvider = XMLProvider()
遗憾
我的想法是,可以删除尖括号里的 JSONProvider 和 XMLProvider 类申明。
在最后,我很满意这个结果,尽管像往常一样,它并不能像我想象的那样 :)。
译者:等待后续的优化吧
引用
本文由 Bill 创作。
最后编辑时间为: 2018.09.07 at 03:58 pm