2016-07-29 90 views
2

我試圖寫一個類,讓我方便地在兩個值之間的插值。如何在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)等一堆函數。

+2

(1)這是更好地使用協議來實現。 (2)只需使用[dclelland/Lerp](https://github.com/dclelland/Lerp)而不是自己寫。 – kennytm

回答

2

的問題是,T是一個通用的佔位符 - 意思是說你不能知道T的實際具體類型是從哪裏來的 功能。因此,儘管您可以有條件地將fromto轉換爲CGRect(因此確定T == CGRect),但Swift無法推斷此信息,因此當它預計返回T時,禁止嘗試返回CGRect

因此,原油的解決辦法是強制轉換返回結果返回給T以彌合與類型的系統信息,這種差距:

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

    // ... 

    return returnRect as! T 
} 

然而,這種類型轉換的是一個真正的表示您正在與類型系統打交道,未利用泛型提供的靜態類型,因此不推薦。

更好的解決方案,如@Wongzigii has already said,是使用的協議。例如,如果你定義一個Interpolate協議,因爲他顯示了他的答案 - 那麼你可以使用此協議,以限制你的通用佔位符Tinterpolate功能:

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 suggested in his answer到您的其他問題 - 以重載運營商實現這一功能。和以前的解決方案一樣,關鍵是定義一個協議,該協議封裝了您在每種類型上定義的功能,然後將通用功能限制爲該協議。

一個簡單的實現可能是這樣的:

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 
    } 
} 
+0

我誤解了@ Wongzigii的答案。我認爲他的意圖是制定CGPoint,CGRect等類將實現的協議,這與僅僅使用它們自己的插值函數擴展這些類沒有多大區別。這是我正在尋找的。但有一個問題 - 如果運算符重載與另一個提供相同的運算符重載兩點或兩個矩形的庫衝突,我該怎麼辦?只需使用中綴操作符? – GoldenJoe

+1

@GoldenJoe取決於運算符重載如何由其他庫實現。如果它與上面的實現相同,那麼你可以簡單地拋棄自己的重載,並依賴庫的重載(但仍然使用協議來約束泛型函數)。如果他們有一個不同的實現(我不能立即想到CGRect + CGRect的邏輯實現,它不只是添加組件) - 那麼你可以在'CGRect'上定義自己的函數來處理加,減等操作 – Hamish

+1

查看[this gist](https://gist.github.com/hamishknight/88157c973cb6db9ba2f701eb5c2495a0)爲例。 – Hamish

2

這將是很好的如果你使用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 "" 
    } 
} 
+2

請注意,通過在協議要求中使用'Self'(例如'static func interpolate(from:Self,to:Self,progress:CGFloat) - > Self'),您可以稍微簡化一點 - 然後在合規中放棄冗餘的類型別類型。 – Hamish

相關問題