2016-07-29 49 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

相关问题