[英]How can I handle different types using generic type in swift?
I'm trying to write a class which allows me to easily interpolate between two values. 我正在尝试编写一个类,使我可以轻松在两个值之间进行插值。
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
}
}
Unfortunately, it gives me an error at return returnRect
: Cannot convert return expression of type 'CGRect' to return type 'T'. 不幸的是,它在
return returnRect
时给了我一个错误:无法将'CGRect'类型的返回表达式转换为'T'类型。 Maybe I'm not understanding how generics are used...I just want to have one function that will handle interpolating between various types, rather than having a bunch of functions like func interpolate(from: Int, to: Int)
, func interpolate(from: CGPoint, to: CGPoint)
, etc. 也许我不了解泛型的用法...我只是想拥有一个可以处理各种类型之间的插值的函数,而不是拥有像
func interpolate(from: Int, to: Int)
, func interpolate(from: CGPoint, to: CGPoint)
,等等。
It would be nice if you use Protocol
to scale your generic type. 如果您使用
Protocol
来扩展通用类型,那将是很好的。
protocol Interpolate {
associatedtype T
static func interpolate(from: T, to: T, progress: CGFloat) -> T
}
Then let CGRect
extension conform to your protocol: 然后让
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)
Also, this would make NSString
conform to protocol Interpolate
easily, like: 此外,这将使
NSString
轻松符合协议Interpolate
,例如:
extension NSString: Interpolate {
typealias T = NSString
static func interpolate(from: T, to: T, progress: CGFloat) -> NSString.T {
//...
return ""
}
}
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. 问题是
T
是一个通用占位符-这意味着您无法从函数中知道T
的实际具体类型是什么。 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
. 因此,尽管你可以有条件地投
from
和to
到CGRect
(从而建立该T == CGRect
),斯威夫特是无法推断出这一信息,因此禁止试图返回一个CGRect
时,预计的回报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: 因此,粗略的解决方案是将返回结果强制转换回
T
,以弥合这种信息与类型系统的差距:
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. 正如@Wongzigii已经说过的 ,更好的解决方案是使用协议。 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: 例如,如果您定义了他在其答案中显示的
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)
}
}
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. 这解决了您的许多问题–消除了运行时类型转换,而是使用协议约束来调用专用
interpolate
函数。 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. 协议约束还防止您在编译时传递不符合
Interpolate
任何类型,因此还解决了类型转换失败时的处理方法。
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. 尽管这么说,但我实际上非常喜欢@JoshCaswell在他对其他问题的回答中建议的解决方案-重载运算符以实现此功能。 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
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.