Swift 4 泛型
in 技术 with 0 comment

Swift 4 泛型

in 技术 with 0 comment

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 类申明。

在最后,我很满意这个结果,尽管像往常一样,它并不能像我想象的那样 :)。

译者:等待后续的优化吧

引用

翻译自:https://medium.com/the-aesthetic-programmer/swift-4-generics-a-generic-class-that-has-a-variable-that-conforms-to-a-protocol-with-associated-9197be69bfa6

Responses