“Protocol can only be used as a generic constraint because it has Self or associated type requirements”的问题

4

我正在尝试生成符合协议Protocoling的ViewModel,该协议是泛型的,并具有相关类型。 有几个ViewModel符合该协议,因此我正在尝试为ViewModel创建工厂。 Swift出现了以下错误: 由于具有Self或相关类型要求,因此协议只能用作泛型约束 示例代码:

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

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


从当前的例子来看,我认为工厂模式并不是最好的选择。也许你可以考虑另一种方式?例如使用带有关联值的枚举(请参阅https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html)。 - Qbyte
@Qbyte 我考虑过,但是枚举代表了我的流程中的一个状态, 根据这个状态我初始化一个ViewModel,问题是,我不想将枚举绑定到ViewModel。 - Gil Polak
很容易解决,只需要在你的数据工厂中添加一个符合泛型类型的条件,可以参考下面我的回答。 :) - Peter Suwara
2个回答

0

这很容易解决,在您的具体工厂实现中,您只需要为您的工厂指定一个符合协议protocoling的泛型,如下所示:

Swift 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是一种很好的入门语言,我相信它的协议和扩展机制使它成为更复杂和有趣的语言之一。

这种设计模式是设置依赖注入等事物的好方法。


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那样工作,我喜欢以下示例:可比较协议(即PAT):

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

这个函数使用了关联类型Self。你想在下一个泛型函数中使用它:

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

在这里,编译器会触发这个错误:协议只能作为泛型约束使用,因为它具有Self或相关类型要求。为什么呢?让我们来看这个例子:

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

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

没有比较番茄和沙拉的理由。相关类型Self不同。为了避免在这种情况下出现错误,您需要将Self类型约束如下:

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

现在您知道了T是可比较的,具有相同的相关类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接