LoginSignup
9
5

More than 3 years have passed since last update.

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

Posted at

この記事は

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

使い方

OptionSetSet<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 で実装する
9
5
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
9
5