如何在swift中使用泛型类型处理不同的类型?

时间:2016-07-29 06:49:20

标签: swift generics

我试图编写一个允许我在两个值之间轻松插值的类。

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处给我一个错误:无法转换类型&#39; CGRect&#39;的返回表达式返回类型&#39; T&#39;。也许我不理解泛型的使用方式......我只想要一个函数来处理各种类型之间的插值,而不是像func interpolate(from: Int, to: Int)func interpolate(from: CGPoint, to: CGPoint)这样的函数,等

2 个答案:

答案 0 :(得分: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 "" 
    }
}

答案 1 :(得分:2)

问题是T是一个通用的占位符 - 这意味着你无法知道函数中T的实际具体类型是什么。因此,虽然您可以有条件地将fromto转换为CGRect(从而确定T == CGRect),但Swift无法推断此信息,因此禁止尝试返回CGRect时预计返回T

因此,原始解决方案是强制将返回结果强制转换回T,以便将这种信息差距与类型系统联系起来:

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

    // ...

    return returnRect as! T
}

但是,这种类型转换实际上是一个标志,表明您正在与类型系统作斗争,而不是利用泛型提供的静态类型,因此不推荐使用。

更好的解决方案,如@Wongzigii has already said,是使用协议。例如,如果您在答案中显示了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 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
    }
}