この記事は
複数のフラグを管理する場合、データサイズを固定長にする目的で 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で実装する