Wan Lutfi Wan Hatta
Wan Lutfi Wan Hatta

Reputation: 332

How to better use type casting in Swift?

I have a problem with swift type casting, let's say I have this variables

let anim = CABasicAnimation(keyPath: "position")
let sublayersCount : NSInteger = 4
let sublayerIdx :NSInteger = 2
let instanceDelay : CGFloat = 0.1;

I want to do some math on them. In objective c, i'd write it like this

anim.beginTime += (sublayersCount - sublayerIdx - 1) * instanceDelay;

But in swift, the same feels very clumsy, is there any way to improve it?

anim.beginTime = CFTimeInterval(CGFloat(anim.beginTime) + CGFloat(sublayersCount - sublayerIdx - 1) * instanceDelay)

Upvotes: 0

Views: 294

Answers (2)

GenericPtr
GenericPtr

Reputation: 686

Type casting in Swift is a nightmare and a joke honestly. I can't believe Apple thought this was good idea anyone wanted. Luckily Swift has powerful operator overloads and type extensions so we can hack around this problem.

This isn't a complete example of all types but the idea is we have operator overloads to cast types up towards larger sizes and/or signs or floats.

For example UInt8 + Float returns a Float (the largest signed type from the operation). UInt + Int returns Int (the same size but casting towards signs). UInt8 + Uint returns UInt (the larger size). The assumption is that we want to preserve signs of numbers and prevent overflowing by casting up to larger sizes (so UInt8(255) + 1.5 returns Float(256.5) instead of truncating or crashing).

Note: I wouldn't recommend putting this in an existing code base because it undermines many implicit assumptions in Swift.

/*

Arithmetic on integers (signed or unsigned) & floats casts up to Float
Arithmetic on signed integers & unsigned integers (of any size) casts up to Int
Arithmetic on unsigned integers & unsigned integers (of any size) casts up to UInt

Float + Int = Float
Float + UInt = Float

UInt + Int = Int
UInt + UInt = UInt
UInt8 + UInt = UInt
UInt8 + Int = Int

*/

// MARK: Floating Point Arithmetic Operators

protocol FloatingPointArithmetic {
    var toFloat: Float { get }
}

func + <T: FloatingPointArithmetic> (lhs: T, rhs: Float) -> Float { return lhs.toFloat + rhs }
func - <T: FloatingPointArithmetic> (lhs: T, rhs: Float) -> Float { return lhs.toFloat - rhs }
func * <T: FloatingPointArithmetic> (lhs: T, rhs: Float) -> Float { return lhs.toFloat * rhs }
func / <T: FloatingPointArithmetic> (lhs: T, rhs: Float) -> Float { return lhs.toFloat / rhs }

// MARK: Integer Arithmetic Operators

protocol IntegerArithmetic: FloatingPointArithmetic {
    var toSigned: Int { get }
    var toUnsigned: UInt { get }
}

protocol SignedIntegerArithmetic: IntegerArithmetic {}
protocol UnsignedIntegerArithmetic: IntegerArithmetic {}

func + <T: SignedIntegerArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> Int { return lhs.toSigned + rhs.toSigned }
func + <T: UnsignedIntegerArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> UInt { return lhs.toUnsigned + rhs.toUnsigned }
func + <T: UnsignedIntegerArithmetic, U: SignedIntegerArithmetic> (lhs: T, rhs: U) -> Int { return lhs.toSigned + rhs.toSigned }

func - <T: SignedIntegerArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> Int { return lhs.toSigned - rhs.toSigned }
func - <T: UnsignedIntegerArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> UInt { return lhs.toUnsigned - rhs.toUnsigned }
func - <T: UnsignedIntegerArithmetic, U: SignedIntegerArithmetic> (lhs: T, rhs: U) -> Int { return lhs.toSigned - rhs.toSigned }

func * <T: SignedIntegerArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> Int { return lhs.toSigned * rhs.toSigned }
func * <T: UnsignedIntegerArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> UInt { return lhs.toUnsigned * rhs.toUnsigned }
func * <T: UnsignedIntegerArithmetic, U: SignedIntegerArithmetic> (lhs: T, rhs: U) -> Int { return lhs.toSigned * rhs.toSigned }

func / <T: SignedIntegerArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> Int { return lhs.toSigned / rhs.toSigned }
func / <T: UnsignedIntegerArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> UInt { return lhs.toUnsigned / rhs.toUnsigned }
func / <T: UnsignedIntegerArithmetic, U: SignedIntegerArithmetic> (lhs: T, rhs: U) -> Int { return lhs.toSigned / rhs.toSigned }

func + <T: FloatingPointArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> Float { return lhs.toFloat + rhs.toFloat }
func + <T: FloatingPointArithmetic, U: SignedIntegerArithmetic> (lhs: T, rhs: U) -> Float { return lhs.toFloat + rhs.toFloat }

func - <T: FloatingPointArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> Float { return lhs.toFloat - rhs.toFloat }
func - <T: FloatingPointArithmetic, U: SignedIntegerArithmetic> (lhs: T, rhs: U) -> Float { return lhs.toFloat - rhs.toFloat }

func * <T: FloatingPointArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> Float { return lhs.toFloat * rhs.toFloat }
func * <T: FloatingPointArithmetic, U: SignedIntegerArithmetic> (lhs: T, rhs: U) -> Float { return lhs.toFloat * rhs.toFloat }

func / <T: FloatingPointArithmetic, U: UnsignedIntegerArithmetic> (lhs: T, rhs: U) -> Float { return lhs.toFloat / rhs.toFloat }
func / <T: FloatingPointArithmetic, U: SignedIntegerArithmetic> (lhs: T, rhs: U) -> Float { return lhs.toFloat / rhs.toFloat }


// MARK: Supported Types

extension Float: FloatingPointArithmetic {
    var toFloat: Float { return self }
}

extension UInt: FloatingPointArithmetic, UnsignedIntegerArithmetic {
    var toSigned: Int { return Int(self) }
    var toUnsigned: UInt { return self }
    var toFloat: Float { return Float(self) }
}

extension UInt8: FloatingPointArithmetic, UnsignedIntegerArithmetic {
    var toSigned: Int { return Int(self) }
    var toUnsigned: UInt { return UInt(self) }
    var toFloat: Float { return Float(self) }
}

extension Int: FloatingPointArithmetic, SignedIntegerArithmetic {
    var toSigned: Int { return self }
    var toUnsigned: UInt { return UInt(self) }
    var toFloat: Float { return Float(self) }
}

Upvotes: 0

Gabriele Petronella
Gabriele Petronella

Reputation: 108169

I agree it's cumbersome. You can work around it by providing ad-hoc overloads, along the lines of

func * (lhs: Double, rhs: CGFloat) -> CGFloat {
    return CGFloat(lhs * Double(rhs))
}

func + (lhs: Double, rhs: CGFloat) -> CGFloat {
    return CGFloat(lhs) + CGFloat(rhs)
}

func - (lhs: Int, rhs: Int) -> Double {
    return lhs - rhs
}

anim.beginTime = CFTimeInterval(anim.beginTime + (sublayersCount - sublayerIdx - 1) * instanceDelay)

Of course this is ridiculously specific to this particular example, but you get the idea.

That being said, there's a library providing a more general spectrum of handy overloads, allowing you to implicitly convert different numeric types. Check it out: https://github.com/seivan/ScalarArithmetic/blob/master/ScalarArithmetic/ScalarArithmetic.swift

Upvotes: 2

Related Questions