T迅捷阵列的替代方案

时间:2019-06-07 03:07:38

标签: swift

在我的测验应用程序中,我初始化了测验,并且提供类在提供问题之前不知道问题的格式(尽管它们受到QuestionProtocol的约束):

public protocol QuestionProtocol {
    init?(fields: [String] )
    var description: String {get}
    var question: String {get}
    var solution: String {get}
    var explainAnswer: String {get}
    var answered: Int {get set}
    var qa: String {get}
    var qb: String {get}
    var qc: String {get}
    var qd: String {get}
}

我可以初始化测验并通过带有签名的方法

轻松地将它们返回
public func initializeQuizzes<T: QuestionProtocol>(with type: T.Type, withCompletionHandler completion: ((Result<[Quiz<T>], Error>) -> Void)?) 

但是提供这些测验很昂贵(API调用或SQL检索),因此我想存储这些测验并从具有签名的合适函数中分别进行检索

public func getNextQFromSet<T: QuestionProtocol>(with type: T.Type) -> (question: T, answers: [String])?

我遇到的问题是存储这些类型为T的问题。

它们链接到测验对象:

public class Quiz<T> {
    private let questions : [T]
    private let name : String

    init(name: String, questions: [T]) {
        self.name = name
        self.questions = questions
    }

    public func getQuestions() -> [T] {
        return questions
    }

    func getName() -> String {
        return name
    }
}

所以我能够将它们存储为符合QuestionProtocol的测验

private var quizzes = [Quiz<QuestionProtocol>]()

但是随后,我丢失了要存储在问题中的多余信息。

我可以存储Any,但是我认为这是不正确的做法

private var anyquizzes = [Quiz<Any>]()

理想情况下,我想存储T

Quiz<T>

但是在Swift中这似乎是不可能的。

由于这些类位于Pod中,因此它们无法了解问题的内部工作原理,并且在运行时向其提供了这些信息,因此使用了泛型以及存储这些问题的困难。

我想不出一种方法来改进App(更具体地说是Pod)的设计-我只想一次初始化测验,然后运行诸如getNextQFromSet()之类的函数来检索相关问题-显然取决于我是否知道问题的类型(在运行时之前不知道)。

为清楚起见,这里是指向Pod的链接:https://github.com/stevencurtis/QuizManager

如何在不知道类型的情况下存储包含这些问题的数组?

4 个答案:

答案 0 :(得分:1)

  

如何在不知道的情况下存储包含这些问题的数组   类型?

据我所知,你做不到。正如rraphael在他的评论中指出的,泛型无法在运行时解析。此外,Arrays迅速用于容纳单一类型:

  

具体来说,您可以使用Array类型保存单个类型的元素,即数组的Element类型。

因此,无论您执行什么操作,都将拥有AnyQuestionProtocol数组,但没有什么比这更动态了:类型将在编译时解析 < / p>

您也许可以根据自己的需求重新设计QuestionProtocol,但是由于没有关于不同类型问题的任何信息,因此,这是一件非常麻烦的事,因为它是体系结构问题,因此很难为您提供更多帮助。

答案 1 :(得分:1)

简而言之,我认为删除QuestionProtocol并将其替换为简单的数据结构struct Question很有意义。

在解释观点之前,我想指出的是,即使我看着吊舱,我仍然不知道所有要求,因此我可能是错的。

让我们尝试从设计的角度而不是从编程语言的角度来看问题。

拥有QuestionProtocol的原因是什么?可以用对象代替它吗?为什么这些属性应该是多态的?当然,实现细节应该被隐藏,但是隐藏数据与协议或附加功能层无关,而与抽象有关。

现在让我们将QuestionProtocol转换为Question对象,并考虑一个抽象。如果存在真正的抽象,则应该有一个对象隐藏数据(详细信息)并公开处理该数据的函数。但是Question对象中没有函数,这意味着后面没有真正的抽象。

最后,这意味着Question实体最有可能是具有公共属性的纯数据结构,并且可以定义为struct Question

现在具有此Question结构,您可以将测验定义为Quiz<Question>,并使用它来保存和检索数据。

此外,我认为值得指出的两点可以简化并可能改善设计和实现:

  1. SQLiteManager为什么对具体问题有所了解(取决于QuestionProtocol)?我认为引入一些通用的DBObject或至少简单的字典[String: Any]很有意义,SQLiteManager会知道如何处理然后插入。然后,Repository可以将Question的数据结构转换为某种程度的合成DBObject,并将其传递给SQLiteManager

  2. 在使用泛型时,大多数情况下无需定义其他type: T.Type参数。定义了泛型后,您可以将其用作[T]T.init等。如果仍然需要元类型(T.Type),则可以通过T.self获得。

希望这会有所帮助!

答案 2 :(得分:0)

您可以使用带有关联值的枚举来描述类型。例如:

struct QuestionTypeA { }
struct QuestionTypeB { }
struct QuestionTypeC { }

enum Question {
    case typeA(question: QuestionTypeA)
    case typeB(question: QuestionTypeB)
    case typeC(question: QuestionTypeC)
}

然后:

public class Quiz {
    private let questions : Question
    private let name : String
    ...

并存储不包含一般知识的测验数组

private var anyquizzes = [Quiz]()

答案 3 :(得分:0)

您将无法使用这两种类型的任何一种在同一数组中存储Quiz<T>Quiz<U>。它们不是同一类型。

如果您有Array<QuizProtocol>,则可以在switch-case语句中与已知类型进行匹配:

var quizzes: [QuizProtocol] = ...

for quiz in quizzes {
    switch quiz {
       case let someQuiz as SomeQuiz:
           ...
       case let someOtherQuiz as SomeOtherQuiz:
           ...
       default: 
           ... // couldn't cast to any known type; do some fallback logic
       ....
    }
}

其中SomeQuiz和SomeOtherQuiz符合QuizProtocol(严格来说,您可以匹配任何类型)。