【Swift】複数のフラグを管理する場合に Set<Enum> を使う

## この記事は

複数のフラグを管理する場合、データサイズを固定長にする目的で `OptionSet` を使うことがあります。
しかしフラグの数が少なく、データサイズを気にしない状況であれば、`OptionSet` のかわりに `Set<Enum>` を使うことで実装を少しシンプルにできます。

## データ型の定義

### `OptionSet`

`OptionSet` はビット集合を表現したプロトコルです。
（ `true` | `false` ）を整数型の各ビットの（ `1` | `0` ）に対応させることで、複数のフラグを１つの 整数型で表現します。

```swift
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` の要素である列挙体はシンプルに次のように定義するだけです。

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

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

```swift
enum ShippingOptions: CaseIterable {
    case nextDay
    case secondDay
    case priority
    case standard
}
```

`allCases` から `Set` を作ることで全体集合になります。

```swift
let universe = Set(ShippingOptions.allCases)
// true
universe.isSuperset(of: [.nextDay, .secondDay])
```

## 使い方

`OptionSet` と `Set<Enum>` はともに `SetAlgebra` に適合しているので、どちらも集合演算を使用することができます。フラグの設定・取得はほとんど同じ操作です。

### `OptionSet`

```swift
var options = ShippingOptions()
options.formUnion([.nextDay, .priority])

if (options == [.nextDay, .priority]) {
    print("お急ぎ")
}
```

### `Set<Enum>`

```swift
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 にブリッジすることができます。

```objC
typedef NS_OPTIONS(NSInteger, ShippingOptions) {
    ShippingOptionsNextDay   = 1 << 0,
    ShippingOptionsSecondDay = 1 << 1,
    ShippingOptionsPriority  = 1 << 2,
    ShippingOptionsStandard  = 1 << 3,

    ShippingOptionsAll = 0b1111,
};
```

Objective-C で上記の定義をすることで、Swift では次のようにインポートされます。

```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` で実装する