簡體   English   中英

如何在 Swift 中使函數的返回類型通用

[英]How to make return type of function generic in 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
    }

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 都有自己的類。 由於集合視圖的重用行為,有必要將輸入的值存儲在模型中

模型負責驗證並返回 - 取決於單元格 - 一個值數組,告訴單元格哪些字段應該獲得紅色邊框(標記為無效)。

這有時可以是 Enum、Int 或 String。

我想達到的目標

正如您可能想象的那樣,在每個子類中使用幾乎相同的驗證方法是很煩人的,因為每次我想在其中一個類上使用該方法時都需要向下轉換

因此,我想保持返回類型開放,即不要在父類中指定特定的類型,因為子類應該能夠返回任何類型。 然后,我會將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()在其中驗證模型
  • 只有子類中 validateModel() 方法的返回類型不同- 所以我想將該方法放在父類 (ElementData) 中,並在子類上覆蓋

這可能嗎,如果可以,怎么辦?

任何幫助,將不勝感激。

這是不可能的。

泛型是干什么用的

假設你有這個功能:

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是返回一個Something 因此, ClassA.validateModel()需要或者返回一個Something ,或者一個子類(奇怪的是,似乎只有傳承關系工作,而不是協議亞型的關系。例如,返回Int其中Any預期不工作)。 由於ClassA.validateModel()返回Array<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
//      
//  }
}

一種解決方法

首先,我們需要確定這些返回類型之間的共同點。 如果我們能做到這一點,那么編譯器就可以回答“someModel 應該是什么類型?”的問題。 以上。

有兩種工具可用:

  1. 類繼承(子類是其超類的子類型)
  2. 協議一致性(協議一致性類型是它們遵守的協議的子類型)

兩者都有優點/缺點。 協議迫使你走上associated-type的痛苦之路,而類則不太靈活(因為它們不能被枚舉或結構子類化)。 在這種情況下,答案取決於您希望此代碼做什么。 從根本上說,您正在嘗試將此數據連接到表格單元格。 因此,為此制定一個協議:

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 )來存儲您需要的數據。

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()
        )
    }
}

一種可能的解決方案是具有關聯類型的協議。 您必須在每個子類中將返回類型指定為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"] }
}

現在編譯器知道所有子類的不同返回類型

在此處輸入圖片說明

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM