LoginSignup
7
7

More than 5 years have passed since last update.

OptionSetTypeでビット演算ができるようにする

Posted at

Swift1.2 → 2.0での変化

Swift1.2 → 2.0でRawOptionSetTypeOptionSetTypeに移り変わり、書き方が変わったのはご存知かと思います。

このOptionSetへの移行でよく記事として見かけるUIUserNotificationTypeを例に出すと、、

1.2まで
let notificationTypes: UIUserNotificationType = .Alert | .Badge | .Sound
2.0移行
let notificationTypes = UIUserNotificationType([.Alert, .Badge, .Sound])
// .Alert | .Badge | .Sound の書き方は×

こんな感じで、 | を使わずに表現する形に移りました。1.2→2.0の移行をした時は大変でしたね :sweat_drops:
書き方が変わった以外にも、OptionSetTypeを1.2の時よりも定義しやすくなったのも大きな変化かと思います。

人によっては、2.0移行での書き方 ([.Alert, .Badge, .Sound])、SomeType.A.union(.B)のが好き!って人もいると思いますが......

& , | , ^ , ~ 使いたくないですか!?

基本的にUIUserNotificationTypeとかみたいにセットするだけ程度なら不要かもしれないですが、
自分でOptionSetType作って、フラグ管理する場合なんかは、

let type = SomeType.A
let other = SomeType.B
var newType = type.union(other) // ビット演算子の | と同等のことをする関数
newType.unionInPlace(.C)
newType.remove(.C)

ってやるよりは、

let type = SomeType.A
let other = SomeType.B
var newType = type | other
newType |= .C
newType &= ~.C

ってやった方が短くなるしスマートな気がします。
自分はC,C++に慣れ親しんできたので、unionを使うよりこっちの書き方のが好きだしわかりやすいです。

ということで...使えるようにしましょう!

CustomOperatorを実装する :bulb:

環境
  • Xcode 7.1.1 (Swift 2.1.1)
    おそらくSwift2.0移行なら大丈夫です。

以下のように、それぞれ必要な演算子を定義していきます。

CustomOperators
func & <T: OptionSetType where T.RawValue: BitwiseOperationsType>(lhs: T, rhs: T) -> T {
    return T(rawValue: lhs.rawValue & rhs.rawValue)
}

func | <T: OptionSetType where T.RawValue: BitwiseOperationsType>(lhs: T, rhs: T) -> T {
    return T(rawValue: lhs.rawValue | rhs.rawValue)
}

func ^ <T: OptionSetType where T.RawValue: BitwiseOperationsType>(lhs: T, rhs: T) -> T {
    return T(rawValue: lhs.rawValue ^ rhs.rawValue)
}

func &= <T: OptionSetType where T.RawValue: BitwiseOperationsType>(inout lhs: T, rhs: T) {
    lhs = lhs & rhs
}

func |= <T: OptionSetType where T.RawValue: BitwiseOperationsType>(inout lhs: T, rhs: T) {
    lhs = lhs | rhs
}

func ^= <T: OptionSetType where T.RawValue: BitwiseOperationsType>(inout lhs: T, rhs: T) {
    lhs = lhs ^ rhs
}

prefix func ~ <T: OptionSetType where T.RawValue: BitwiseOperationsType>(type: T) -> T {
    return T(rawValue: ~type.rawValue)
}

これで、 & , &= , | , |= , ^ , ^= , ~ , が使えるようになります :blush:
それぞれの演算子はもともとビット演算子として用意されているので、オーバーロードという形となります。

ポイントは、OptionSetTyperawValueBitwiseOperationsTypeに適合するという条件を加えることで、
lhsrhsrawValueでビット演算をおこなえるようになり、演算を行った結果をrawValueとして渡して新たなOptionSetTypeを返しています。

ということで、冒頭で出してた例で再現すると、

let h = UIUserNotificationType([.Alert, .Badge, .Sound])
let g: UIUserNotificationType = .Alert | .Badge | .Sound // OK!!!!
// let g = UIUserNotificationType.Alert | .Badge | .Sound // これもOK。
//一番最初のTypeに型を書けばあとのTypeに対して型推論が働くらしい。
print(h == g) // true、2つとも同値であることが確認できた!!

~(チルダ) も実装したので、こんなこともできます :muscle:
あくまでも例なので、実用的かどうかは一旦置いておきます。

上記の演算子を定義して、Playgroundに貼り付ければすぐ実行できます!
import UIKit
import Foundation
import XCPlayground

let corners = UIRectCorner.AllCorners & ~.TopLeft // 下の行と同じ意味になる
//let corners = UIRectCorner.TopRight | .BottomLeft | .BottomRight
let view = UIView(frame: CGRectMake(0, 0, 100, 100))
view.backgroundColor = UIColor.redColor()
view.clipsToBounds = true
let path = UIBezierPath(
    roundedRect: view.bounds,
    byRoundingCorners: corners,
    cornerRadii: CGSizeMake(50.0, 50.0)
)
let layer = CAShapeLayer()
layer.frame = view.bounds
layer.path = path.CGPath
view.layer.mask = layer
XCPlaygroundPage.currentPage.liveView = view // 左上以外が角丸のviewが描画される

A & ~Bで、AからBのビットを除いた結果を返すことができます。
ビット演算については、以下が参考になるかと思います。

あとはおこのみで、OptionSetTypeと、OptionSetTypeのRawValueと同じ型の数値と大小比較できる比較演算子を定義して、以下の処理ができるようにするといいかもしれないですね :smirk:

typeがセットされているか調べる
var type = UIRectCorner.TopRight | .BottomLeft
if (type & .TopRight) > 0 { //typeがセットされているか調べる
//if (type & .TopRight).rawValue > 0 { ">"を実装しなければ、こうするのが正解
    print("TopRight is Set!!")
}

これでだいぶ、CやC++の時のような扱い方ができるようになったかと思います!では!

あとがき(Swiftのソースを覗いてみる) :pushpin:

当初は、

最初に書いたもの
func | <T: OptionSetType>(lhs: T, rhs: T) -> T {
    return lhs.union(rhs) //ただのラッパー程度の実装
}
...

のように定義していたのですが、実際にunion ( | ), intersect ( & ), exclusiveOr ( ^ )とかをどうやって組んでいるだろうということで本家(swift/stdlib/public/core/OptionSet.swift)を :mag: 見にいったら、

OptionalSet.swiftでの実装から抜粋
extension OptionSetType {
  /// Returns the set of elements contained in `self`, in `other`, or in
  /// both `self` and `other`.
  @warn_unused_result
  public func union(other: Self) -> Self {
    var r: Self = Self(rawValue: self.rawValue)
    r.unionInPlace(other)
    return r
  }
...
}

extension OptionSetType where RawValue : BitwiseOperationsType {
  /// Create an empty instance.
  ///
  /// - Equivalent to `[] as Self`
  public init() {
    self.init(rawValue: .allZeros)
  }

  /// Insert all elements of `other` into `self`.
  ///
  /// - Equivalent to replacing `self` with `self.union(other)`.
  /// - Postcondition: `self.isSupersetOf(other)`
  public mutating func unionInPlace(other: Self) {
    self = Self(rawValue: self.rawValue | other.rawValue)
  }

  /// Remove all elements of `self` that are not also present in
  /// `other`.
  ///
  /// - Equivalent to replacing `self` with `self.intersect(other)`
  /// - Postcondition: `self.isSubsetOf(other)`
  public mutating func intersectInPlace(other: Self) {
    self = Self(rawValue: self.rawValue & other.rawValue)
  }

  /// Replace `self` with a set containing all elements contained in
  /// either `self` or `other`, but not both.
  ///
  /// - Equivalent to replacing `self` with `self.exclusiveOr(other)`
  public mutating func exclusiveOrInPlace(other: Self) {
    self = Self(rawValue: self.rawValue ^ other.rawValue)
  }
}

って書いてあるのを見つけて、intersectとかを書いてたら結果として処理が深くなっちゃうしなって思って、参考にしたのが今の形となります。


意外に、使える絵文字色々あるんですね :notes:

7
7
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
7
7