在我的测验应用程序中,我初始化了测验,并且提供类在提供问题之前不知道问题的格式(尽管它们受到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
如何在不知道类型的情况下存储包含这些问题的数组?
答案 0 :(得分:1)
如何在不知道的情况下存储包含这些问题的数组 类型?
据我所知,你做不到。正如rraphael在他的评论中指出的,泛型无法在运行时解析。此外,Arrays迅速用于容纳单一类型:
具体来说,您可以使用Array类型保存单个类型的元素,即数组的Element类型。
因此,无论您执行什么操作,都将拥有Any
或QuestionProtocol
数组,但没有什么比这更动态了:类型将在编译时解析 < / p>
您也许可以根据自己的需求重新设计QuestionProtocol
,但是由于没有关于不同类型问题的任何信息,因此,这是一件非常麻烦的事,因为它是体系结构问题,因此很难为您提供更多帮助。
答案 1 :(得分:1)
简而言之,我认为删除QuestionProtocol
并将其替换为简单的数据结构struct Question
很有意义。
在解释观点之前,我想指出的是,即使我看着吊舱,我仍然不知道所有要求,因此我可能是错的。
让我们尝试从设计的角度而不是从编程语言的角度来看问题。
拥有QuestionProtocol
的原因是什么?可以用对象代替它吗?为什么这些属性应该是多态的?当然,实现细节应该被隐藏,但是隐藏数据与协议或附加功能层无关,而与抽象有关。
现在让我们将QuestionProtocol
转换为Question
对象,并考虑一个抽象。如果存在真正的抽象,则应该有一个对象隐藏数据(详细信息)并公开处理该数据的函数。但是Question
对象中没有函数,这意味着后面没有真正的抽象。
最后,这意味着Question
实体最有可能是具有公共属性的纯数据结构,并且可以定义为struct Question
。
现在具有此Question
结构,您可以将测验定义为Quiz<Question>
,并使用它来保存和检索数据。
此外,我认为值得指出的两点可以简化并可能改善设计和实现:
SQLiteManager
为什么对具体问题有所了解(取决于QuestionProtocol
)?我认为引入一些通用的DBObject
或至少简单的字典[String: Any]
很有意义,SQLiteManager
会知道如何处理然后插入。然后,Repository
可以将Question
的数据结构转换为某种程度的合成DBObject
,并将其传递给SQLiteManager
。
在使用泛型时,大多数情况下无需定义其他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(严格来说,您可以匹配任何类型)。