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

后端 未结 2 1426
遥遥无期
遥遥无期 2021-01-23 04:08

I\'m trying to write a class which allows me to easily interpolate between two values.

class Interpolation
{
    class func interpolate(from: T, to: T,          


        
相关标签:
2条回答
  • 2021-01-23 04:46

    The problem is that T is a generic placeholder – meaning that you cannot know what the actual concrete type of T is from within the function. Therefore although you are able to conditionally cast from and to to a CGRect (thus establishing that T == CGRect), Swift is unable to infer this information and therefore prohibits attempting to return a CGRect when it expects a return of T.

    The crude solution therefore is to force cast the return result back to T in order to bridge this gap in information with the type-system:

    if let from = from as? CGRect, let to = to as? CGRect {
    
        // ...
    
        return returnRect as! T
    }
    

    However, this kind of type-casting is really a sign that you're fighting the type-system and not taking advantage of the static typing that generics offer, and therefore is not recommended.

    The better solution, as @Wongzigii has already said, is to use a protocol. For example, if you define an Interpolate protocol as he shows in his answer – you can then use this protocol in order to constrain your generic placeholder T in your interpolate function:

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

    This solves many of your problems – it does away with the runtime type-casting and instead uses the protocol constraint in order to call the specialised interpolate function. The protocol constraint also prevents you from passing any types that don't conform to Interpolate at compile-time, and therefore also solves the problem of what to do when your type-casting fails.

    Although that being said, I actually quite like the solution that @JoshCaswell suggested in his answer to your other question – overloading operators in order to achieve this functionality. As with the previous solution, the key is to define a protocol that encapsulates the functionality you're defining on each type, and then constrain your generic function to this protocol.

    A simple implementation may look like this:

    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 讨论(0)
  • 2021-01-23 04:46

    It would be nice if you use Protocol to scale your generic type.

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

    Then let CGRect extension conform to your protocol:

    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)
    

    Also, this would make NSString conform to protocol Interpolate easily, like:

    extension NSString: Interpolate {
        typealias T = NSString
        static func interpolate(from: T, to: T, progress: CGFloat) -> NSString.T {
            //...
            return "" 
        }
    }
    
    0 讨论(0)
提交回复
热议问题