簡體   English   中英

如何快速使用通用類型處理不同類型?

[英]How can I handle different types using generic type in swift?

我正在嘗試編寫一個類,使我可以輕松在兩個值之間進行插值。

class Interpolation
{
    class func interpolate<T>(from: T, to: T, progress: CGFloat) -> T
    {
        // Safety
        assert(progress >= 0 && progress <= 1, "Invalid progress value: \(progress)")

        if let a = from as? CGFloat, let b = to as? CGFloat
        {
        }
        if let a = from as? CGPoint, let b = to as? CGPoint
        {

        }
        if let from = from as? CGRect, let to = to as? CGRect
        {
            var returnRect = CGRect()
            returnRect.origin.x     = from.origin.x + (to.origin.x-from.origin.x) * progress
            returnRect.origin.y     = from.origin.y + (to.origin.y-from.origin.y) * progress
            returnRect.size.width   = from.size.width + (to.size.width-from.size.width) * progress
            returnRect.size.height  = from.size.height + (to.size.height-from.size.height) * progress
            return returnRect // Cannot convert return expression of type 'CGRect' to return type 'T'
        }

        return from
    }
}

不幸的是,它在return returnRect時給了我一個錯誤:無法將'CGRect'類型的返回表達式轉換為'T'類型。 也許我不了解泛型的用法...我只是想擁有一個可以處理各種類型之間的插值的函數,而不是擁有像func interpolate(from: Int, to: Int)func interpolate(from: CGPoint, to: CGPoint) ,等等。

如果您使用Protocol來擴展通用類型,那將是很好的。

protocol Interpolate {
    associatedtype T
    static func interpolate(from: T, to: T, progress: CGFloat) -> T
}

然后讓CGRect擴展符合您的協議:

extension CGRect: Interpolate {
    typealias T = CGRect
    static func interpolate(from: T, to: T, progress: CGFloat) -> CGRect.T {
        var returnRect = CGRect()
        returnRect.origin.x     = from.origin.x + (to.origin.x-from.origin.x) * progress
        returnRect.origin.y     = from.origin.y + (to.origin.y-from.origin.y) * progress
        returnRect.size.width   = from.size.width + (to.size.width-from.size.width) * progress
        returnRect.size.height  = from.size.height + (to.size.height-from.size.height) * progress
        return returnRect
    }
}

var from = CGRect(x: 0, y: 0, width: 1, height: 1) // (0, 0, 1, 1)
var to = CGRect(x: 1, y: 1, width: 0, height: 0)   // (1, 1, 0, 0)

CGRect.interpolate(from, to: to, progress: 1)      // (1, 1, 0, 0)

此外,這將使NSString輕松符合協議Interpolate ,例如:

extension NSString: Interpolate {
    typealias T = NSString
    static func interpolate(from: T, to: T, progress: CGFloat) -> NSString.T {
        //...
        return "" 
    }
}

問題是T是一個通用占位符-這意味着您無法從函數中知道T的實際具體類型是什么。 因此,盡管你可以有條件地投fromtoCGRect (從而建立該T == CGRect ),斯威夫特是無法推斷出這一信息,因此禁止試圖返回一個CGRect時,預計的回報T

因此,粗略的解決方案是將返回結果強制轉換回T ,以彌合這種信息與類型系統的差距:

if let from = from as? CGRect, let to = to as? CGRect {

    // ...

    return returnRect as! T
}

但是,這種類型轉換實際上表明您正在與類型系統作斗爭,沒有利用泛型提供的靜態類型,因此不建議這樣做。

正如@Wongzigii已經說過的 ,更好的解決方案是使用協議。 例如,如果您定義了他在其答案中顯示的Interpolate協議,則可以使用此協議,以便將通用占位符T限制在interpolate函數中:

class Interpolation {
    class func interpolate<T:Interpolate>(from: T, to: T, progress: CGFloat) -> T {

        // Safety
        assert(progress >= 0 && progress <= 1, "Invalid progress value: \(progress)")

        return T.interpolate(from: from, to: to, progress: progress)
    }
}

這解決了您的許多問題–消除了運行時類型轉換,而是使用協議約束來調用專用interpolate函數。 協議約束還防止您在編譯時傳遞不符合Interpolate任何類型,因此還解決了類型轉換失敗時的處理方法。

盡管這么說,但我實際上非常喜歡@JoshCaswell在他對其他問題的回答中建議的解決方案-重載運算符以實現此功能。 與以前的解決方案一樣,關鍵是要定義一個協議,該協議封裝您要在每種類型上定義的功能,然后將通用函數限制為該協議。

一個簡單的實現可能看起來像這樣:

protocol Interpolatable {
    func +(lhs:Self, rhs:Self) -> Self
    func -(lhs:Self, rhs:Self) -> Self
    func *(lhs:Self, rhs:CGFloat) -> Self
}

func +(lhs:CGRect, rhs:CGRect) -> CGRect {
    return CGRect(x: lhs.origin.x+rhs.origin.x,
                  y: lhs.origin.y+rhs.origin.y,
                  width: lhs.size.width+rhs.size.width,
                  height: lhs.size.height+rhs.size.height)
}

func -(lhs:CGRect, rhs:CGRect) -> CGRect {
    return CGRect(x: lhs.origin.x-rhs.origin.x,
                  y: lhs.origin.y-rhs.origin.y,
                  width: lhs.size.width-rhs.size.width,
                  height: lhs.size.height-rhs.size.height)
}

func *(lhs:CGRect, rhs:CGFloat) -> CGRect {
    return CGRect(x: lhs.origin.x*rhs,
                  y: lhs.origin.y*rhs,
                  width: lhs.size.width*rhs,
                  height: lhs.size.height*rhs)
}

extension CGRect : Interpolatable {}
extension CGFloat : Interpolatable {}

class Interpolation {
    class func interpolate<T:Interpolatable>(from: T, to: T, progress: CGFloat) -> T {
        assert(progress >= 0 && progress <= 1, "Invalid progress value: \(progress)")
        return from + (to - from) * progress
    }
}

暫無
暫無

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

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