タイトルが少し難しそうに聞こえますが、内容は割と簡単です。
たまたま、自分用に書いているSwiftのextensionsを整理していた時に、「あ、こうした方がいいんじゃないかな」って思ったので、それを試したまとめになります。
例えば、CGPointやCGSizeといったCGGeometryのstruct群があるのですが、デフォルトのままだと四則演算が使えないです。
// 期待するのは、CGPoint(x:50, y: 100)
let p1 = CGPoint(x:10, y: 20)
let p2 = p1 * 5 // error!!
なので、以下のようにして、四則演算をそれぞれ定義していきます。(CGPointのみ掲載します)
import Foundation
import UIKit
extension CGPoint {
init(_ x: CGFloat, _ y: CGFloat) {
self.init(x: x, y: y)
}
init(_ x: Int, _ y: Int) {
self.init(x: x, y: y)
}
init(_ x: Double, _ y: Double) {
self.init(x: x, y: y)
}
}
func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(lhs.x + rhs.x, lhs.y + rhs.y)
}
func += (inout lhs: CGPoint, rhs: CGPoint) {
lhs = lhs + rhs
}
func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(lhs.x - rhs.x, lhs.y - rhs.y)
}
func -= (inout lhs: CGPoint, rhs: CGPoint) {
lhs = lhs - rhs
}
func * (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(lhs.x * rhs.x, lhs.y * rhs.y)
}
func *= (inout lhs: CGPoint, rhs: CGPoint) {
lhs = lhs * rhs
}
func * (point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(point.x * scalar, point.y * scalar)
}
func *= (inout point: CGPoint, scalar: CGFloat) {
point = point * scalar
}
func * (point: CGPoint, scalar: Double) -> CGPoint {
return point * CGFloat(scalar)
}
func *= (inout point: CGPoint, scalar: Double) {
point = point * scalar
}
func * (point: CGPoint, scalar: Int) -> CGPoint {
return point * CGFloat(scalar)
}
func *= (inout point: CGPoint, scalar: Int) {
point = point * scalar
}
func / (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(lhs.x / rhs.x, lhs.y / rhs.y)
}
func /= (inout lhs: CGPoint, rhs: CGPoint) {
lhs = lhs / rhs
}
func / (point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(point.x / scalar, point.y / scalar)
}
func /= (inout point: CGPoint, scalar: CGFloat) {
point = point / scalar
}
func / (point: CGPoint, scalar: Double) -> CGPoint {
return point / CGFloat(scalar)
}
func /= (inout point: CGPoint, scalar: Double) {
point = point / scalar
}
func / (point: CGPoint, scalar: Int) -> CGPoint {
return point / CGFloat(scalar)
}
func /= (inout point: CGPoint, scalar: Int) {
point = point / scalar
}
一部に着目してみます。
func * (point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(point.x * scalar, point.y * scalar)
}
func *= (inout point: CGPoint, scalar: CGFloat) {
point = point * scalar
}
func * (point: CGPoint, scalar: Double) -> CGPoint {
return point * CGFloat(scalar)
}
func *= (inout point: CGPoint, scalar: Double) {
point = point * scalar
}
func * (point: CGPoint, scalar: Int) -> CGPoint {
return point * CGFloat(scalar)
}
func *= (inout point: CGPoint, scalar: Int) {
point = point * scalar
}
この処理、やってる個と同じなのに、引数の型が違うだけじゃん!
って気が付きましたでしょうか。
もちろんこれは関数のオーバーロードなので書き方が間違っているわけではないですが、これだと
- Uint8にも対応したい!
といった具合に対応したい型が増えてしまうと、それだけオーバーロードする関数の数が増えて、冗長且つメンテナンスしづらいコードになっていきます。
そんなときは、 Protocol をうまく使って対処していきます。
以下のように、 CGGeometryCalculable を定義します。
protocol CGGeometryCalculable {
var value: CGFloat { get }
}
これにより、 CGGeometryCalculable を採用したstruct,class等では、CGFloat
型の値を返すvalue
を実装しないといけないようになります。
これを、適用したい型に採用していきます。
protocol CGGeometryCalculable {
var value: CGFloat { get }
}
extension Int: CGGeometryCalculable {
var value: CGFloat {
return CGFloat(self)
}
}
extension Float: CGGeometryCalculable {
var value: CGFloat {
return CGFloat(self)
}
}
extension Double: CGGeometryCalculable {
var value: CGFloat {
return CGFloat(self)
}
}
extension CGFloat: CGGeometryCalculable {
var value: CGFloat {
return self
}
}
これで、先ほど着目した関数が、
func * (point: CGPoint, scalar: CGGeometryCalculable) -> CGPoint {
return CGPoint(point.x * scalar.value, point.y * scalar.value)
}
func *= (inout point: CGPoint, scalar: CGGeometryCalculable) {
point = point * scalar
}
と スッキリ まとめられます!
先にprotocolを定義して、あれこれ型に適応する手間はあるものの、 ロジックの複製が避けられる ので、
メンテナンスしやすくなります。
ちなみにこのような手法は、 にも見られます。
渡すURLのパラメータが、String
でも、NSURL
でも可能にするために、 URLStringConvertible というprotocolを実装しています。
public protocol URLStringConvertible {
var URLString: String { get }
}
extension String: URLStringConvertible {
public var URLString: String {
return self
}
}
extension NSURL: URLStringConvertible {
public var URLString: String {
return absoluteString
}
}
extension NSURLComponents: URLStringConvertible {
public var URLString: String {
return URL!.URLString
}
}
extension NSURLRequest: URLStringConvertible {
public var URLString: String {
return URL!.URLString
}
}
これにより、
public func upload(method: Method, _ URLString: URLStringConvertible, headers: [String: String]? = nil, file: NSURL) -> Request
というメソッドがあったときに、
upload(method, "http://hogehoge.com", headers: nil, file:NSURL(file://hogehoge)!)
upload(method, NSURL(string: "http://hogehoge.com")!, headers: nil, file:NSURL(file://hogehoge)!)
と、String
でもNSURL
でも許容されるようになります。でも、メソッドは 1つ です。
関数のオーバーロードも魅力的で使い勝手がいいですが、 これ、うまくまとめられないかな? となったら、 protocol で解決できないかな?って考えてみるのもありかもしれません。