协议问题只能用作一般约束,因为它具有自我或相关类型要求。

时间:2018-11-11 09:00:21

标签: ios swift

我正在尝试生成一个符合协议Protocoling的ViewModel,该协议是通用的,并且具有关联的类型。 有一些符合协议的ViewModel,因此我正在尝试为viewModel创建一个工厂。 我已经通过Swift掩盖了以下错误: Protocol can only be used as a generic constraint because it has Self or associated type requirements 示例代码:

protocol Protocoling {
    associatedtype modulingType
    var data: modulingType { get }
}

enum MyTypes {
    case myName
    case myAddress
}

class NameViewModel: Protocoling {
    let data: String

    init(name: String) {
        data = name
    }
}

class AddressViewModel: Protocoling {
    let data: [String]
    init(address: [String]) {
        data = address
    }
}

class DataFactory {
    func viewModel(forType type: MyTypes) -> Protocoling {
        switch type {
            case .name: return NameViewModel(name: "Gil")
            case .address: return AddressViewModel(address: ["Israel", "Tel Aviv"])
        }
    }
}

错误出现在func viewModel(forType type: MyTypes) -> Protocoling中。

有没有办法解决这个问题?

2 个答案:

答案 0 :(得分:0)

您可以使用具有关联类型(PAT)的协议作为返回类型,而无需更多约束,因为编译器需要知道要使用哪种类型。

在您的情况下,您必须使用一种称为类型擦除的技术才能使用任何 协议

class AnyProtocoling: Protocoling {
  let data: Any

  init<U: Protocoling>(_ viewModel: U) {
    self.data = viewModel.data as Any
  }
}

class DataFactory {
  func viewModel(forType type: MyTypes) -> AnyProtocoling {
    switch type {
    case .myName:
      return AnyProtocoling(NameViewModel(name: "Gil"))
    case .myAddress:
      return AnyProtocoling(AddressViewModel(address: ["Israel", "Tel Aviv"]))
    }
  }
}

这将允许您“擦除”协议的关联类型并返回视图模型的Any版本。

为了理解为什么PAT需要如此工作,我喜欢以下示例:Equatable协议(它是PAT):

static func ==(lhs: Self, rhs: Self) -> Bool

此函数使用Self类型,它是一个关联类型。您想在下一个通用函数中使用它:

func areEquals(left: Equatable, right: Equatable) -> Bool {
  return left == right
}

此处,编译器将触发此错误:Protocol can only be used as a generic constraint because it has Self or associated type requirements。为什么?让我们举个例子:

struct tomato: Equatable {}
struct salad: Equatable {}

areEquals(left: tomato(), right: salad())

没有理由比较西红柿和沙拉。关联的类型Self不同。为了避免这种情况下的错误,您需要按以下方式约束Self类型:

func areEquals<T: Equatable>(left: T, right: T) -> Bool

现在,您知道T是相等的,并且具有相同的关联类型。

答案 1 :(得分:0)

这很容易修复,在具体的工厂实现中,您只需要为工厂指定必须符合协议protocoling的泛型即可,请参见以下代码:

快捷键4

protocol Protocoling {
    associatedtype modulingType
    var data: modulingType { get }
}

enum MyTypes {
    case myName
    case myAddress
}

class NameViewModel: Protocoling {
    let data: String

    init(name: String) {
        data = name
    }
}

class AddressViewModel: Protocoling {
    let data: [String]
    init(address: [String]) {
        data = address
    }
}

class DataFactory<T> where T: Protocoling {    
    func viewModel(forType type: MyTypes) -> T? {
        switch type {
        case .myName: return NameViewModel(name: "Gil") as? T
        case .myAddress: return AddressViewModel(address: ["Israel", "Tel Aviv"]) as? T
        default: return nil /* SUPPORT EXTENSION WITHOUT BREAKING */
        }
    }
}

这是进入带有协议的奇妙抽象世界的第一步。您真的可以用它创造一些惊人的东西。虽然,我不得不说,就个人而言,它不像继承那样直观,但它是创建解耦和抽象系统的一个小难题,实际上更强大。

Swift是一种很棒的入门语言,我相信它的协议和扩展机制使其成为更复杂和有趣的语言之一。

这种设计模式是设置诸如依赖注入之类的好方法。