この記事は
複数のフラグを管理する場合、データサイズを固定長にする目的で OptionSet
を使うことがあります。
しかしフラグの数が少なく、データサイズを気にしない状況であれば、OptionSet
のかわりに Set<Enum>
を使うことで実装を少しシンプルにできます。
データ型の定義
OptionSet
OptionSet
はビット集合を表現したプロトコルです。
( true
| false
)を整数型の各ビットの( 1
| 0
)に対応させることで、複数のフラグを1つの 整数型で表現します。
struct ShippingOptions: OptionSet {
let rawValue: Int
static let nextDay = ShippingOptions(rawValue: 1 << 0)
static let secondDay = ShippingOptions(rawValue: 1 << 1)
static let priority = ShippingOptions(rawValue: 1 << 2)
static let standard = ShippingOptions(rawValue: 1 << 3)
}
Set<Enum>
Set
の要素である列挙体はシンプルに次のように定義するだけです。
enum ShippingOptions {
case nextDay
case secondDay
case priority
case standard
}
Set<Enum>
は列挙型の値を要素をもつ集合です。集合内の要素の有無によってフラグを表現します。
OptionSet
の場合とは異なり、要素をビットに格納する方法を用いないので、raw-value-style-enum である必要はありません。もちろん、raw-value-style-enum でも良いです。
rawValue
の型は何でも良く、Int
である必要もありません。
全体集合
OptionSet
では全体集合を自前で定義する必要がありますが、
列挙型であれば CaseIterable
で自動合成可能です。
OptionSet
struct ShippingOptions: OptionSet {
let rawValue: Int
static let nextDay = ShippingOptions(rawValue: 1 << 0)
static let secondDay = ShippingOptions(rawValue: 1 << 1)
static let priority = ShippingOptions(rawValue: 1 << 2)
static let standard = ShippingOptions(rawValue: 1 << 3)
/// 全体集合
static let all: ShippingOptions = [nextDay, secondDay, priority, standard]
}
Set<Enum>
enum ShippingOptions: CaseIterable {
case nextDay
case secondDay
case priority
case standard
}
allCases
から Set
を作ることで全体集合になります。
let universe = Set(ShippingOptions.allCases)
// true
universe.isSuperset(of: [.nextDay, .secondDay])
使い方
OptionSet
と Set<Enum>
はともに SetAlgebra
に適合しているので、どちらも集合演算を使用することができます。フラグの設定・取得はほとんど同じ操作です。
OptionSet
var options = ShippingOptions()
options.formUnion([.nextDay, .priority])
if (options == [.nextDay, .priority]) {
print("お急ぎ")
}
Set<Enum>
var options = Set<ShippingOptions>()
options.formUnion([.nextDay, .priority])
if (options == [.nextDay, .priority]) {
print("お急ぎ")
}
複数のフラグを OptionSet
で定義する理由
Set<Enum>
の方がシンプルに定義できたりと優位性があるように見えますが、UIKit などのフレームワークでは OptionSet
で定義されています。
Swift で作った Set<Enum>
では Objective-C にブリッジすることができないからです。
しかし、Objective-C で作った NS_OPTIONS
であれば OptionSet
な構造体として Swift にブリッジすることができます。
typedef NS_OPTIONS(NSInteger, ShippingOptions) {
ShippingOptionsNextDay = 1 << 0,
ShippingOptionsSecondDay = 1 << 1,
ShippingOptionsPriority = 1 << 2,
ShippingOptionsStandard = 1 << 3,
ShippingOptionsAll = 0b1111,
};
Objective-C で上記の定義をすることで、Swift では次のようにインポートされます。
public struct ShippingOptions: OptionSet {
public init(rawValue: Int)
public static var nextDay: ShippingOptions { get }
public static var secondDay: ShippingOptions { get }
public static var priority: ShippingOptions { get }
public static var standard: ShippingOptions { get }
public static var all: ShippingOptions { get }
}
Objective-C と Swift の両方をサポートする場合は、NS_OPTIONS
で定義することになります。
まとめ
-
OptionSet
は複数のフラグを固定長データに詰め込むことができる -
Set
でもOptionSet
と同じ操作ができる -
Set
で実装した方がシンプル - Objective-C と Swift の両方をサポートしたい場合は
NS_OPTIONS
で実装する