LoginSignup
1
3

More than 3 years have passed since last update.

NumberConvertibleの紹介

Last updated at Posted at 2020-03-17

この記事は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にあります。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3