この記事はhttps://academy.realm.io/jp/posts/richard-fox-casting-swift-2/を元にしています。
Swiftもバージョンアップされて当時と状況も変わってきたので、いつくかアップデートしてます。
Swiftは厳格な静的型付け言語です。
そのため
let num1: Int = 1
let num2: Double = 2.0
let num3: CGFloat = num1 + num2
のような計算は行えません。
しかし、ビット演算や金額処理などを除いてモバイルアプリの数値計算にここまで厳格な数値への型付けは必要でしょうか?
そのため今回は演算子のオーバーロードとプロトコルを活用して、Swiftの型安全性を少し犠牲に簡易的な演算手法を紹介します。
最初に思いつくのが、全ての型同士の演算に対して演算子を定義してしまうと言う手法です。しかしこれでは必要な演算子の数は膨大になり、コンパイラが型を決定するのにかかる時間も長くなってしまいます。
そのためprotocol
を使って一般化し、様々な以下のように型同士で演算ができるようにします。
実装
まずは以下のようなprotocol
を定義します。
protocol Dividable {
static func / (lhs: Self, rhs: Self) -> Self
}
protocol NumberConvertible: Numeric, Dividable {
init (_ value: Int)
init (_ value: Float)
init (_ value: Double)
init (_ value: CGFloat)
}
Numeric
には除算が定義されてないため、Dividable
を定義するととで、除算に対応しています。
そして、各数値型をNumberConvertible
に対応させます。
extension Double : NumberConvertible {}
extension Float : NumberConvertible {}
extension Int : NumberConvertible {}
extension CGFloat : NumberConvertible{
init(_ value: CGFloat){
self = value
}
}
CGFloat
にはCGFloat
からのイニシャライザは提供されていないので、その部分だけ定義します。
変換用のメソッドとしてconvert()
を定義します。
extension NumberConvertible {
func convert<T: NumberConvertible>() -> T {
switch self {
case let x as CGFloat: return T(x)
case let x as Float: return T(x)
case let x as Double: return T(x)
case let x as Int: return T(x)
default:
fatalError("NumberConvertible convert cast failed!")
}
}
}
最後に各演算子を定義してあげます。
func + <T: NumberConvertible, U: NumberConvertible, V: NumberConvertible>(rhs: T, lhs: U) -> V {
let v: V = lhs.convert()
let w: V = rhs.convert()
return v + w
}
func - <T: NumberConvertible, U: NumberConvertible, V: NumberConvertible>(rhs: T, lhs: U) -> V {
let v: V = lhs.convert()
let w: V = rhs.convert()
return v - w
}
func * <T: NumberConvertible, U: NumberConvertible, V: NumberConvertible>(rhs: T, lhs: U) -> V {
let v: V = lhs.convert()
let w: V = rhs.convert()
return v * w
}
func / <T: NumberConvertible, U: NumberConvertible, V: NumberConvertible>(rhs: T, lhs: U) -> V {
let v: V = lhs.convert()
let w: V = rhs.convert()
return v / w
}
代入演算子も
func += <T: NumberConvertible, U: NumberConvertible>(rhs: inout T, lhs: U) {
rhs = rhs.convert() + lhs.convert()
}
func -= <T: NumberConvertible, U: NumberConvertible>(rhs: inout T, lhs: U) {
rhs = rhs.convert() - lhs.convert()
}
func *= <T: NumberConvertible, U: NumberConvertible>(rhs: inout T, lhs: U) {
rhs = rhs.convert() * lhs.convert()
}
func /= <T: NumberConvertible, U: NumberConvertible>(rhs: inout T, lhs: U) {
rhs = rhs.convert() / lhs.convert()
}
最後にOptional型に対して ??
を定義し
簡易変換用に独自演算子 ~~
を定義します。
func ?? <T: NumberConvertible, U: NumberConvertible, V:NumberConvertible>(lhs: T?, rhs: U) -> V {
let v: V? = lhs?.convert()
return v == nil ? rhs.convert() : v!
}
prefix operator ~~
prefix func ~~<T: NumberConvertible, U: NumberConvertible>(lhs: T) -> U {
return lhs.convert()
}
以上で完成です。
使い方
let num1: Int = 2
let num2: Double = 3.14
let num3: CGFloat = num1 + num2
let view = UIView()
let width: Int = 320
let height: Double = 100.5
view.frame.size.width = ~~width
view.frame.size.height = ~~height
速度の調整などをしたものは
https://github.com/ObuchiYuki/NumberConvertible/blob/master/NumberConvertible.swiftにあります。