使用通用约束

Working with generic constraints

我知道以前有人问过这个问题,但我不知道如何解决当前的问题。我已经用 associatedtype 属性:

定义了一个协议 MultipleChoiceQuestionable
protocol Questionable {
    var text: String {get set}
    var givenAnswer: String? {get set}
}

protocol MultipleChoiceQuestionable: Questionable {
    associatedtype Value
    var answers: Value { get }
}

struct OpenQuestion: Questionable {
    var text: String
    var givenAnswer: String?
}

struct MultipleChoiceQuestion: MultipleChoiceQuestionable {
    typealias Value = [String]
    var text: String
    var givenAnswer: String?
    var answers: Value
}

struct NestedMultipleChoiceQuestion: MultipleChoiceQuestionable {
    typealias Value = [MultipleChoiceQuestion]
    var text: String
    var answers: Value
    var givenAnswer: String?
} 

符合此协议的类型保存在一个数组中,如Questionable,如下所示:

// This array contains OpenQuestion, MultipleChoiceQuestion and NestedMultipleChoiceQuestion
private var questions: [Questionable] = QuestionBuilder.createQuestions()

在我的代码中某处我想做类似的事情:

let question = questions[index]
if let question = question as? MultipleChoiceQuestionable { 
   // Do something with the answers
      question.answers = .....
}

这是不可能的,因为 Xcode 警告我:Protocol MultipleChoiceQuestionable can only be used as a generic constraint. 我一直在寻找如何解决这个问题问题,因为泛型对我来说很新。显然 Swift 在编译期间不知道 associatedtype 的类型,这就是引发此错误的原因。我读过有关使用类型擦除的信息,但我不知道这是否能解决我的问题。也许我应该改用通用属性,或者我的协议定义有误?

如果你想对你的子协议对象应用的操作不依赖于关联类型(即既没有通用参数也没有 returns 通用类型)你可以引入一个辅助协议,它只是公开你需要的properties/methods,让你的类型符合那个协议,并根据那个协议声明question

例如,如果您只想了解有关该问题的一些信息:

protocol MultipleChoiceInfo {
  var numberOfAnswers: Int { get }
}

extension MultipleChoiceQuestion: MultipleChoiceInfo {
  var numberOfAnswers: Int { return answers.count }
}
// do the same for the other multiple-choice types

然后你可以像这样通过新协议访问问题:

let question = questions[index]
if let info = question as? MultipleChoiceInfo {
  print(info.numberOfAnswers)
}

正如我所说,如果您不能提供抽象(非通用)接口,那么这将不起作用。

编辑

如果您需要处理问题中的通用数据,您可以根据具体的通用类型将逻辑提取到另一种 "processing" 类型中,该类型为您的问题提供接口。然后每个问题类型将其数据分派到处理器接口:

protocol MultipleChoiceProcessor {
  func process(stringAnswers: [String])
  func process(nestedAnswers: [MultipleChoiceQuestion])
}

protocol MultipleChoiceProxy {
  func apply(processor: MultipleChoiceProcessor)
}

extension MultipleChoiceQuestion: MultipleChoiceProxy {
  func apply(processor: MultipleChoiceProcessor) {
    processor.process(stringAnswers: answers)
  }
}

只需创建一个符合 MultipleChoiceProcessor 的类型,然后再次进行类型检查:

if let proxy = question as? MultipleChoiceProxy {
  proxy.apply(processor:myProcessor)
}

顺便说一句,如果您在实际应用程序中没有更多的协议和结构,您也可以完全放弃协议的东西……对于这种问题,它似乎有点过度设计。