【问题标题】:Working with generic constraints使用通用约束
【发布时间】:2018-02-07 17:53:47
【问题描述】:

我知道以前有人问过这个问题,但我不知道如何解决当前的问题。我已经定义了一个带有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 只能用作泛型约束。我一直在寻找如何解决这个问题,因为泛型对我来说很新。显然 Swift 在编译期间不知道 associatedtype 的类型,这就是引发此错误的原因。我读过有关使用类型擦除的信息,但我不知道这是否能解决我的问题。也许我应该改用通用属性,或者我的协议定义错误?

【问题讨论】:

    标签: swift generics swift-protocols


    【解决方案1】:

    如果您要应用于子协议对象的操作不依赖于关联类型(即既没有泛型参数也没有返回泛型类型),您可以引入仅公开属性/方法的辅助协议您需要,让您的类型符合该协议,并根据该协议声明 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)
    }
    

    正如我所说,如果您不能提供抽象(非泛型)接口,那么这是行不通的。

    编辑

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

    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)
    }
    

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

    【讨论】:

    • 感谢您的回复!这个辅助协议真的很棒,我以前从未见过这样的设计模式。问题是我真的需要这个辅助协议中无法定义的答案属性..但也许我应该在我的问题中更好地提到这一点,我很抱歉。我说这个问题没有其他解决方案是正确的吗?如果是这样,我将不得不重新考虑架构。再次感谢!
    • 即使您的用例可能可以在没有协议的情况下以更简单的方式解决,但它仍然是了解协议的一个很好的例子:) 当然还有另一种解决方案......我编辑了我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-26
    • 2016-01-09
    • 1970-01-01
    • 2020-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多