lin*_*ram 2 arrays class generic-programming swift
在我的应用程序中,我有一个名为“ElementData”的超类和几个继承自它的子类。
每个子类都有自己的 validateModel() 方法,它返回不同的类型,具体取决于类——总是在一个数组中。
换句话说:该方法在每个子类中返回不同的类型。
A类: func validateModel() -> [String]
B类: func validateModel() -> [Int]
C类: func validateModel() -> [MyCustomEnum]
如您所见,只有返回值彼此不同。
编辑:validateModel() 方法示例:
A类:
func validateModel() -> [DefaultElementFields]{ // DefaultElementFields is an enum with the different view types of my collection view
var checkResult: [DefaultElementFields] = []
if name == "" {
checkResult.append(.Name)
}
if Int(rewardedPoints) == nil {
checkResult.append(.Points)
}
if description == "" {
checkResult.append(.Description)
}
if selectedImage == nil {
checkResult.append(.Image)
}
return checkResult
}
Run Code Online (Sandbox Code Playgroud)
B类:
func validateModel() -> [Int] { // returns the index of the text field which is wrong
var checkResult: [Int] = []
let filledValues = codes.filter {
$0 != ""
}
if filledValues.count == 0 { // if no values have been entered, all fields should be marked red.
checkResult.append(-1)
return checkResult
}
for (i, code) in codes.enumerated() {
if code != "" && (code.count < 3 || code.count > 10 || code.rangeOfCharacter(from: NSCharacterSet.alphanumerics.inverted) != nil){ // code must have more than 3 and less than 11 characters. No symbols are allowed.
checkResult.append(i)
}
}
return checkResult
}
Run Code Online (Sandbox Code Playgroud)
编辑:课程的用途:
这些类基本上存储用户输入到集合视图单元格中的数据,例如文本、数字或日期。每个 CollectionViewCellType 都有自己的类。由于集合视图的重用行为,有必要将输入的值存储在模型中。
该模型还负责验证并返回 - 取决于单元格 - 一个值数组,告诉单元格哪些字段应该获得红色边框(标记为无效)。
这有时可以是 Enum、Int 或 String。
正如您可能想象的那样,在每个子类中使用几乎相同的验证方法是很烦人的,因为每次我想在其中一个类上使用该方法时都需要向下转换。
因此,我想保持返回类型开放,即不要在父类中指定特定的类型,因为子类应该能够返回任何类型。然后,我会将validateModel()方法移动到父类中并覆盖其子类中的方法。
我想到了一个带有泛型的解决方案(如果可能的话)。
这是我对整个事情的通用方法:
class ElementData {
func validateModel<T>() -> [T] {
return [1] as! [T] // just a test return
}
}
Run Code Online (Sandbox Code Playgroud)
以及方法的调用:
dataObject.validateModel() // dataObject inherits from ElementData -> has access to validateModel()
Run Code Online (Sandbox Code Playgroud)
不幸的是,它不起作用,我收到以下错误:
“无法推断通用参数‘T’”
这可能吗,如果可以,怎么办?
任何帮助,将不胜感激。
这是不可能的。
假设你有这个功能:
func identity(_ value: Any) -> Any {
return value
}
Run Code Online (Sandbox Code Playgroud)
它实际上不起作用:
let i = 5
assert(identity(i) == i) // ? binary operator '==' cannot be applied to operands of type 'Any' and 'Int'
Run Code Online (Sandbox Code Playgroud)
Any导致类型信息丢失。尽管我们看到参数的类型和返回值总是相同的,但我们还没有向类型系统表达这一点。这是泛型类型参数的完美用例。它允许我们表达参数类型和返回值之间的关系。
func identity<T>(_ value: T) -> T {
return value
}
let i = 5
assert(identity(i) == i) // ?
Run Code Online (Sandbox Code Playgroud)
回顾你的问题,你会发现这里没有类型关系要表达。
ClassA.validateModel() 总是回来 [String]ClassB.validateModel() 总是回来 [Int]ClassC.validateModel() 总是回来 [MyCustomEnum]那不是通用的。
假设您有一个类型为 的对象ElementData。该对象可以是ElementData、 或ClassA、 或ClassB、 或的实例ClassC。鉴于所有这四种类型都是可能的,并且假设存在一些混合物来执行您想要的操作,那么这段代码将如何工作?
let elementData = someElementData()
let validatedModel = elementData.validateModel() // What type is `someValue` supposed to be?
Run Code Online (Sandbox Code Playgroud)
由于我们(也不是编译器)知道 value 的具体类型是什么elementData(我们只知道它是 anElementData或其子类之一),那么编译器应该如何确定 的类型validatedModel ?
此外,您的代码将违反 Liskov 替换原则。ClassA需要支持在ElementData预期的地方被替换。其中一个说的事情ElementData.validateModel()可以做的是回报Something。因此,ClassA.validateModel()需要要么返回Something,或子类(奇怪的是,似乎只有传承关系工作,而不是协议亚型的关系。例如,回到Int这里Any预计不工作)。由于ClassA.validateModel()ReturnsArray<String>并且Array不是类(因此,不能有超类),因此没有可能的类型Something可用于使代码不违反 LSP 并进行编译。
这是 LSP 的说明,以及协方差如何在覆盖方法的返回类型中工作,而不是在覆盖方法的参数类型中工作。
// https://www.mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html
class Animal {}
class Cat: Animal {}
class Person {
func purchaseAnimal() -> Animal {
return Animal()
}
}
class CrazyCatLady: Person {
// Totally legal. `Person` has to be able to return an `Animal`.
// A `Cat` is an animal, so returning a `Cat` where an `Animal` is required is totally valid
override func purchaseAnimal() -> Cat {
return Cat()
}
// This method definition wouldn't be legal, because it violates the Liskov Substitution Principle (LSP).
// A `CrazyCatLady` needs to be able to stand in anywhere a `Person` can be used. One of the things a
// `Person` can do is to `pet(animal: Animal)`. But a `CrazyCatLady` can't, because she can only pet cats.
//
// If this were allowed to compile, this could would be undefined behaviour:
//
// let person: Person = getAPerson()
// let animal: Animal = getAnAnimal()
// person.pet(animal)
//
// override func pet(animal: Cat) { // ? method does not override any method from its superclass
//
// }
}
Run Code Online (Sandbox Code Playgroud)
首先,我们需要确定这些返回类型之间的共同点。如果我们能做到这一点,那么编译器就可以回答“someModel 应该是什么类型?”的问题。以上。
有两种工具可用:
两者都有优点/缺点。协议迫使你陷入绝望的痛苦之路associated-type,而类则不太灵活(因为它们不能被枚举或结构子类化)。在这种情况下,答案取决于您希望此代码做什么。从根本上说,您正在尝试将此数据连接到表格单元格。因此,为此制定一个协议:
protocol CellViewDataSource {
func populate(cellView: UICellView) {
// adjust the cell as necessary.
}
}
Run Code Online (Sandbox Code Playgroud)
现在,更新您的方法以返回此类型:
class ElementData {
func validateModel() -> CellViewDataSource {
fatalError()
}
}
class ClassA {
func validateModel() -> CellViewDataSource {
fatalError()
}
}
Run Code Online (Sandbox Code Playgroud)
要实现这些方法,您必须扩展Array以符合CellViewDataSource. 然而,这是一个非常可怕的想法。我建议您创建一个新类型(可能是 a struct)来存储您需要的数据。
struct ModelA {
let name: String
let points: Int
let description: String
let image: UIImage
}
extension ModelA: CellViewDataSource {
func populate(cellView: UICellView) {
// Populate the cell view with my `name`, `points`, `description` and `image`.
}
}
class ElementData {
func validateModel() -> CellViewDataSource {
fatalError("Abstract method.")
}
}
class ClassA {
func validateModel() -> CellViewDataSource {
return ModelA(
name: "Bob Smith",
points: 123,
description: "A dummy model.",
image: someImage()
)
}
}
Run Code Online (Sandbox Code Playgroud)
一种可能的解决方案是具有关联类型的协议。您必须typealias在每个子类中指定返回类型。
protocol Validatable {
associatedtype ReturnType
func validateModel() -> [ReturnType]
}
class ElementData {}
class SubClassA : ElementData, Validatable {
typealias ReturnType = Int
func validateModel() -> [Int] { return [12] }
}
class SubClassB : ElementData, Validatable {
typealias ReturnType = String
func validateModel() -> [String] { return ["Foo"] }
}
Run Code Online (Sandbox Code Playgroud)
现在编译器知道所有子类的不同返回类型