Swiftで複数のフラグを管理するためにOptionSetを使うと便利だった

サービスを作っているとき、ユーザーの状態(e.g. 課金)によって特定の機能が解放されていることがあります。それを管理するにはフラグのような仕組みを使って管理するのがよくある手段なのですが、それが複数になってしまうと管理のコストが高くなってしまいます。その複数のフラグを管理するために[OptionSet](https://developer.apple.com/reference/swift/optionset)を使うと便利でした。

## Boolによるフラグ管理

```swift
struct Permission {
    let isAllownA: Bool // Aという機能が開放されているかのフラグ
    let isAllownB: Bool // Bという機能が開放されているかのフラグ
    let isAllownC: Bool // Cという機能が開放されているかのフラグ
    let isAllownD: Bool // Dという機能が開放されているかのフラグ
    let isAllownE: Bool // Eという機能が開放されているかのフラグ
    let isAllownF: Bool // Fという機能が開放されているかのフラグ
}
```

管理するフラグが多くなってくるとツライですね...

## OptionSetを用いたフラグ管理

```swift
struct Permission: OptionSet {
    let rawValue: Int

    static let A = Permission(rawValue: 1 << 0)
    static let B = Permission(rawValue: 1 << 1)
    static let C = Permission(rawValue: 1 << 2)
    static let D = Permission(rawValue: 1 << 3)
    static let E = Permission(rawValue: 1 << 4)
    static let F = Permission(rawValue: 1 << 5)

    init(rawValue: Int) {
        self.rawValue = rawValue
    }

    init(rawValues: [Int]) {
        self.rawValue = rawValues.reduce(0) { $0 + (1 << $1) }
    }
}
```

今回の`OptionSet`を用いたフラグの管理はビット列を用いています。各機能のフラグは各ビットのフラグが立っているか見れば分かります。Aという機能が開放されているかどうかは1ビット目を見れば分かり、同様にBという機能が開放されているかは2ビット目を見れば分かります。C ~ Fも同様です。

以下のように、`[1, 3, 5]`という`Array<Int>`はB, D, Fという機能が開放されていることを示しており、それをPermissionのコンストラクタで与えています。生成されたPermissionの`.contains(member: Permission)`メソッドに知りたい機能を与えてあげるとビットが立っているかどうかを返してくれます。

```swift
let permission = Permission(rawValue: [1, 3, 5]) // 101010みたいなイメージ
permission.contains(.A) // false
permission.contains(.B) // true
permission.contains(.C) // false
permission.contains(.D) // true
permission.contains(.E) // false
permission.contains(.F) // true
```

## OptionSetによるフラグ管理の良い点

### 同値判定が楽に書ける

例えば、ユーザーがある行動をした際に特定の機能が開放されたかどうかのテストを書くとき権限の同値判定を行いたいケースを考えます。その際に`Permission`オブジェクト同士を比較する際に使う`==()`の処理が`OptionSet`を用いた`Permission`では簡単に書けます。

```swift
// Boolを用いたフラグ管理のEquatable
extension Permission: Equatable {
    static func == (lhs: Permission, rhs: Permission) -> Bool {
        return lhs.isAllowedA == rhs.isAllowedA
            && lhs.isAllowedB == rhs.isAllowedB
            && lhs.isAllowedC == rhs.isAllowedC
            && lhs.isAllowedD == rhs.isAllowedD
            && lhs.isAllowedE == rhs.isAllowedE
            && lhs.isAllowedF == rhs.isAllowedF
    }
}
```

```swift
// OptionSetを用いたフラグ管理のEquatable
extension Permission: Equatable {
    static func == (lhs: Permission, rhs: Permission) -> Bool {
        return lhs.rawValue == rhs.rawValue
    }
}
```

Boolを用いたフラグ管理の同値判定はそれぞれのプロパティの等式を比較して、それのAND条件を評価します。プロパティがすべてBoolなので同じプロパティ同士を比べているかどうかは型的には解決できず、新しいフラグが追加されたときは`==()`の処理を更新しなければなりません。
それに比べて、OptionSetを用いたフラグ管理の同値判定は`rawValue`の等式を評価すればよいだけで、これは管理するべきフラグが増えた際も常に同様です。

## OptionSetによるフラグ管理の悪い点

### Permissionへのマッピングが大変

クライアント内でフラグ管理が完結しているとそこまで問題はないと思うのですが、APIのレスポンスなどでフラグの状態を取得する際はその値を`Permission`にオブジェクトにマッピングするのが大変です。

以下のようなレスポンスで返ってくると、結局Aがtrueなら1, Bがtrueなら2のようなマッピングをしなくてはなりません。これは結局、複数の方法で管理するのでそのマッピング部分のコストが高くなってしまいます。

```json
{
    "A": true,
    "B": false,
    "C": true,
    "D": false,
    "E": true,
    "F": true
}
```

APIのレスポンスが`Array<Int>`のような形であればOptionSetを用いたフラグ管理を考えてみる価値はあると思います。

```json
[
    0,
    2,
    4,
    5
]
```

## 参考
https://developer.apple.com/reference/swift/optionset