如何在 Swift 中使 return 类型的函数通用
How to make return type of function generic in Swift
简介
在我的应用程序中,我有一个名为 "ElementData" 的 superclass 和 几个继承的子 classes来自它。
Each child class has its own validateModel() method which returns
a different type, depending on the class - always in an Array.
换句话说:方法return每个子class的不同类型class。
例子
Class答:func validateModel() -> [String]
Class B: func validateModel() -> [Int]
Class C: func validateModel() -> [MyCustomEnum]
如您所见,只有 return 个值彼此不同。
编辑:validateModel() 方法示例:
Class答:
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
}
Class乙:
func validateModel() -> [Int] { // returns the index of the text field which is wrong
var checkResult: [Int] = []
let filledValues = codes.filter {
[=13=] != ""
}
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
}
编辑:classes 的用途:
class基本上存储数据用户输入到集合视图单元格,例如文本、数字或日期。每个 CollectionViewCellType 都有自己的 class。由于集合视图的重用行为,有必要将输入的值存储在模型.
中
模型还负责验证和returns - 取决于单元格 - 值数组告诉单元格哪些字段应该有红色边框(标记为无效)。
这有时可以是 Enum、Int 或 String。
我想达到的目标
正如您想象的那样,在每个 child-class 中使用几乎相同的 validationMethods 是非常烦人的,因为有必要 downcast 每次我想在其中一个 class 上使用该方法时。
因此,我想保留 return 类型打开 即不要在父 class 中指定一个特定的作为子class 应该可以 return 任何类型。然后,我会将 validateModel() 方法移动到父 class 并覆盖其子 classes.
中的方法
我想到了 generics 的解决方案(如果可能的话)。
我试过的
这是我对整个事情的通用方法:
class ElementData {
func validateModel<T>() -> [T] {
return [1] as! [T] // just a test return
}
}
以及方法的调用:
dataObject.validateModel() // dataObject inherits from ElementData -> has access to validateModel()
不幸的是,它不起作用,我收到以下错误:
"Generic parameter 'T' could not be inferred"
总结:
- 我有一个 superclass "ElementData" 和几个 subclasses(classes 继承)
- 每个 subclass 都有一个验证模型的方法 validateModel()
- 只有 return 类型 的 validateModel() 方法在 subclasses differ -所以我想把这个方法放在父 class (ElementData) 里面,只是 override 它在 subclasses
这可能吗,如果可能,怎么办?
如有任何帮助,我们将不胜感激。
这是不可能的。
泛型的用途
假设你有这个功能:
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
导致类型信息丢失。尽管我们看到参数的类型和 return 值总是相同的,但我们还没有向类型系统表达这一点。这是泛型类型参数的完美 use-case。它允许我们表达参数类型和 return 值之间的关系。
func identity<T>(_ value: T) -> T {
return value
}
let i = 5
assert(identity(i) == i) // ✅
哪些泛型不适合
回过头来看你的问题,你发现这里没有类型关系可以表达。
ClassA.validateModel()
总是 returns [String]
ClassB.validateModel()
总是 returns [Int]
ClassC.validateModel()
总是 returns [MyCustomEnum]
这不是通用的。
它是如何工作的?
假设您有一个 ElementData
类型的对象。该对象可以是 ElementData
、ClassA
、ClassB
或 ClassC
的实例。鉴于所有这四种类型都是可能的,并且假设存在一些混合来做你想做的事,这段代码将如何工作?
let elementData = someElementData()
let validatedModel = elementData.validateModel() // What type is `someValue` supposed to be?
因为我们(也不是编译器)知道 elementData
的值是什么具体类型(我们只知道它是一个 ElementData
,或其子 class 之一), 编译器应该如何确定 validatedModel
的类型?
此外,您的代码将违反 Liskov 替换原则。 ClassA
需要支持在需要 ElementData
的地方被替换。 ElementData.validateModel()
可以做的事情之一是return一个Something
。因此,ClassA.validateModel()
需要 return 一个 Something
,或者一个子 class(奇怪的是,似乎只有继承关系起作用,而不是协议子类型关系。例如,returning Int
预期 Any
不起作用)。由于 ClassA.validateModel()
returns Array<String>
,并且 Array
不是 class(因此,不能有 superclass),因此有没有可用于使代码不违反 LSP 和编译的可能类型 Something
。
这是 LSP 的说明,以及 Covariance 如何在 return 类型的重写方法中工作,而不是在重写方法的参数类型中工作。
// 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
//
// }
}
一种解决方法
首先,我们需要确定这些 return 类型之间的共同点。如果我们能做到这一点,那么编译器就可以回答“someModel 应该是什么类型?”的问题。以上。
有两个工具可用:
- Class 继承(subclasses 是其 superclasses 的子类型)
- 协议一致性(协议一致性类型是它们所遵循的协议的子类型)
两者都有advantages/disadvantages。协议迫使你走上 associated-type
痛苦的绝望之路,而 Classes 则不那么灵活(因为它们不能被枚举或结构子 classed)。在这种情况下,答案取决于您希望这段代码做什么。从根本上说,您正在尝试将此数据连接到 table 单元格。因此,为此制定一个协议:
protocol CellViewDataSource {
func populate(cellView: UICellView) {
// adjust the cell as necessary.
}
}
现在,将您的方法更新为 return 这种类型:
class ElementData {
func validateModel() -> CellViewDataSource {
fatalError()
}
}
class ClassA {
func validateModel() -> CellViewDataSource {
fatalError()
}
}
要实现这些方法,您必须扩展 Array
以符合 CellViewDataSource
。然而,这是一个非常糟糕的想法。相反,我建议您创建一个新类型(可能是 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()
)
}
}
一种可能的解决方案是具有关联类型的协议。您必须在每个子类中将 return 类型指定为 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"] }
}
现在编译器知道所有子类的不同 return 类型
简介
在我的应用程序中,我有一个名为 "ElementData" 的 superclass 和 几个继承的子 classes来自它。
Each child class has its own validateModel() method which returns a different type, depending on the class - always in an Array.
换句话说:方法return每个子class的不同类型class。
例子
Class答:func validateModel() -> [String]
Class B: func validateModel() -> [Int]
Class C: func validateModel() -> [MyCustomEnum]
如您所见,只有 return 个值彼此不同。
编辑:validateModel() 方法示例:
Class答:
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
}
Class乙:
func validateModel() -> [Int] { // returns the index of the text field which is wrong
var checkResult: [Int] = []
let filledValues = codes.filter {
[=13=] != ""
}
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
}
编辑:classes 的用途:
class基本上存储数据用户输入到集合视图单元格,例如文本、数字或日期。每个 CollectionViewCellType 都有自己的 class。由于集合视图的重用行为,有必要将输入的值存储在模型.
中模型还负责验证和returns - 取决于单元格 - 值数组告诉单元格哪些字段应该有红色边框(标记为无效)。
这有时可以是 Enum、Int 或 String。
我想达到的目标
正如您想象的那样,在每个 child-class 中使用几乎相同的 validationMethods 是非常烦人的,因为有必要 downcast 每次我想在其中一个 class 上使用该方法时。
因此,我想保留 return 类型打开 即不要在父 class 中指定一个特定的作为子class 应该可以 return 任何类型。然后,我会将 validateModel() 方法移动到父 class 并覆盖其子 classes.
中的方法我想到了 generics 的解决方案(如果可能的话)。
我试过的
这是我对整个事情的通用方法:
class ElementData {
func validateModel<T>() -> [T] {
return [1] as! [T] // just a test return
}
}
以及方法的调用:
dataObject.validateModel() // dataObject inherits from ElementData -> has access to validateModel()
不幸的是,它不起作用,我收到以下错误:
"Generic parameter 'T' could not be inferred"
总结:
- 我有一个 superclass "ElementData" 和几个 subclasses(classes 继承)
- 每个 subclass 都有一个验证模型的方法 validateModel()
- 只有 return 类型 的 validateModel() 方法在 subclasses differ -所以我想把这个方法放在父 class (ElementData) 里面,只是 override 它在 subclasses
这可能吗,如果可能,怎么办?
如有任何帮助,我们将不胜感激。
这是不可能的。
泛型的用途
假设你有这个功能:
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
导致类型信息丢失。尽管我们看到参数的类型和 return 值总是相同的,但我们还没有向类型系统表达这一点。这是泛型类型参数的完美 use-case。它允许我们表达参数类型和 return 值之间的关系。
func identity<T>(_ value: T) -> T {
return value
}
let i = 5
assert(identity(i) == i) // ✅
哪些泛型不适合
回过头来看你的问题,你发现这里没有类型关系可以表达。
ClassA.validateModel()
总是 returns[String]
ClassB.validateModel()
总是 returns[Int]
ClassC.validateModel()
总是 returns[MyCustomEnum]
这不是通用的。
它是如何工作的?
假设您有一个 ElementData
类型的对象。该对象可以是 ElementData
、ClassA
、ClassB
或 ClassC
的实例。鉴于所有这四种类型都是可能的,并且假设存在一些混合来做你想做的事,这段代码将如何工作?
let elementData = someElementData()
let validatedModel = elementData.validateModel() // What type is `someValue` supposed to be?
因为我们(也不是编译器)知道 elementData
的值是什么具体类型(我们只知道它是一个 ElementData
,或其子 class 之一), 编译器应该如何确定 validatedModel
的类型?
此外,您的代码将违反 Liskov 替换原则。 ClassA
需要支持在需要 ElementData
的地方被替换。 ElementData.validateModel()
可以做的事情之一是return一个Something
。因此,ClassA.validateModel()
需要 return 一个 Something
,或者一个子 class(奇怪的是,似乎只有继承关系起作用,而不是协议子类型关系。例如,returning Int
预期 Any
不起作用)。由于 ClassA.validateModel()
returns Array<String>
,并且 Array
不是 class(因此,不能有 superclass),因此有没有可用于使代码不违反 LSP 和编译的可能类型 Something
。
这是 LSP 的说明,以及 Covariance 如何在 return 类型的重写方法中工作,而不是在重写方法的参数类型中工作。
// 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
//
// }
}
一种解决方法
首先,我们需要确定这些 return 类型之间的共同点。如果我们能做到这一点,那么编译器就可以回答“someModel 应该是什么类型?”的问题。以上。
有两个工具可用:
- Class 继承(subclasses 是其 superclasses 的子类型)
- 协议一致性(协议一致性类型是它们所遵循的协议的子类型)
两者都有advantages/disadvantages。协议迫使你走上 associated-type
痛苦的绝望之路,而 Classes 则不那么灵活(因为它们不能被枚举或结构子 classed)。在这种情况下,答案取决于您希望这段代码做什么。从根本上说,您正在尝试将此数据连接到 table 单元格。因此,为此制定一个协议:
protocol CellViewDataSource {
func populate(cellView: UICellView) {
// adjust the cell as necessary.
}
}
现在,将您的方法更新为 return 这种类型:
class ElementData {
func validateModel() -> CellViewDataSource {
fatalError()
}
}
class ClassA {
func validateModel() -> CellViewDataSource {
fatalError()
}
}
要实现这些方法,您必须扩展 Array
以符合 CellViewDataSource
。然而,这是一个非常糟糕的想法。相反,我建议您创建一个新类型(可能是 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()
)
}
}
一种可能的解决方案是具有关联类型的协议。您必须在每个子类中将 return 类型指定为 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"] }
}
现在编译器知道所有子类的不同 return 类型