如何在Swift

时间:2019-07-17 19:47:45

标签: arrays swift class generic-programming

简介

在我的应用程序中,我有一个超类,称为“ ElementData” ,并且有从其继承的几个子类。

  

每个子类都有其自己的validateModel()方法,该方法返回   不同的类型,取决于类-始终在数组中。

换句话说::该方法在每个子类中返回不同的Types。

示例

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
    }

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
    }

编辑:这些类的用途:

用户基本上是存储数据类,例如进入集合视图单元格,例如文字,数字或日期。每个CollectionViewCellType都有自己的类。由于集合视图的重用行为,有必要将输入的值存储在模型中。

模型负责验证,并根据单元格返回一个值数组,该值告诉单元格哪些字段应显示红色边框(标记为无效)。

有时可以是枚举,整数或字符串。

我想实现的目标

您可能会想像,每个子类中都有几乎相同的validateMethods,这很烦人,因为每次我想使用 downcast 时都必须方法之一。

因此,我想保持返回类型为打开状态,即不要在父类中指定特定的类型,因为子类应该能够返回任何类型。然后,我将 validateModel()方法移到父类中,并在其子类中覆盖该方法。

我想到了使用泛型的解决方案(如果可能)。

我尝试过的

这是我处理整件事的通用方法:

class ElementData {

    func validateModel<T>() -> [T] {
        return [1] as! [T] // just a test return
    }

}

以及该方法的调用:

dataObject.validateModel() // dataObject inherits from ElementData -> has access to validateModel()

不幸的是,它不起作用,并且出现以下错误:

  

“无法推断出通用参数'T'”

摘要:

  • 我有一个超类“ ElementData”和几个子类(继承的类)
  • 每个子类都有一个validateModel()方法,其中的模型已通过验证
  • 在子类 differ 中仅validateModel()方法的返回类型-因此,我想将该方法放在父类(ElementData)中,而仅< strong>在子类上覆盖

有可能吗?

任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:2)

这是不可能的。

什么是泛型

假设您具有此功能:

func identity(_ value: Any) -> Any {
    return value
}

实际上不起作用:

let i = 5
assert(identity(i) == i) // ❌ binary operator '==' cannot be applied to operands of type 'Any' and 'Int'

Any导致类型信息丢失。即使我们看到参数的类型和返回值始终是相同的,我们也没有将其表达给类型系统。这是泛型类型参数的理想用例。它使我们能够表达参数类型与返回值之间的关系。

func identity<T>(_ value: T) -> T {
    return value
}

let i = 5
assert(identity(i) == i) // ✅

哪些泛型不适合

回头看看您的问题,您会发现这里没有类型关系可以表达。

  • ClassA.validateModel()始终返回[String]
  • ClassB.validateModel()始终返回[Int]
  • ClassC.validateModel()始终返回[MyCustomEnum]

这不是通用的。

它怎么会工作?

假设您有一个ElementData类型的对象。该对象可以是ElementDataClassAClassBClassC的实例。假设所有这四种类型都是可能的,并且假设存在某种合意的功能来执行您想要的操作,那么这段代码将如何工作?

let elementData = someElementData()
let validatedModel = elementData.validateModel() // ? What type is `someValue` supposed to be?

由于我们(也不是编译器)知道elementData的值是什么具体类型(我们只知道它是ElementData或其子类之一),编译器应该怎么做?确定validatedModel的类型?

此外,您的代码将违反Liskov替代原则。 ClassA需要支持在预期ElementData处被替换。 ElementData.validateModel()可以做的一件事就是返回Something。因此,ClassA.validateModel()需要返回一个Something或一个子类(奇怪的是,似乎只有继承关系有效,而协议子类型关系无效。例如,返回Int其中{{1 }}无效。由于Any返回ClassA.validateModel(),并且Array<String>不是类(因此,不能有超类),因此不可能使用类型Array来使该代码不会违反LSP并进行编译。

下面是LSP的说明,以及协方差在覆盖方法的返回类型中如何工作,而不是在覆盖方法的参数类型中如何工作。

Something

现在,更新您的方法以返回此类型:

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

# One approach to a solution

First of all, we need to establish what's in common between these return types. If we could do that, then the compiler has an answer to the question of "what type should someModel be?" above.

There are two tools available:

1) Class inheritance (subclasses are subtypes of their superclasses)
2) Protocol conformance (protocol conforming types are subtypes of the protocols they conform to)

Both have advantages/disadvantages. Protocols force you in the painful road of dispair that is `associated-type`, whereas Classes are less flexible (since they can't be subclassed by enums or structs). In this case, the answer lies with what you want this code to do. Fundamentally, you're trying to hook this data up to a table cell. So make a protocol for that:

``` Swift
protocol CellViewDataSource {
    func populate(cellView: UICellView) {
        // adjust the cell as necessary.
    }
} 

要实现这些方法,您必须扩展class ElementData { func validateModel() -> CellViewDataSource { fatalError() } } class ClassA { func validateModel() -> CellViewDataSource { fatalError() } } 使其符合Array。但是,这是一个非常糟糕的想法。我建议您创建一个新类型(可能是CellViewDataSource)来存储所需的数据。

struct

答案 1 :(得分:1)

可能的解决方案是具有关联类型的协议。您必须在每个子类中将返回类型指定为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"] }
}

现在,编译器知道所有子类的不同返回类型

enter image description here