Swift 2.0 の新しいOption、OptionSetType

Swift 2.0では、Optionの型が変わりました。 あたらしく導入された OptionSetType についてちょっと見てみました。

OptionSetTypeとは

最近、Swift1.2の既存プロジェクトのSwift2.0対応を少しずつ始めたんですが、UIUserNotificationType の option 設定でコンパイルエラーが発生。 こんな感じのコードを書いていたんですが、「Binary operator '|' cannot be applied to two UIUserNotificationType operands」というエラーが表示されていました。

let types : UIUserNotificationType = 
    UIUserNotificationType.Badge |
    UIUserNotificationType.Alert |
    UIUserNotificationType.Sound        
let settins : UIUserNotificationSettings = UIUserNotificationSettings(forTypes: types, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settins)

また、こちらのUIView の animateWithDurationでも同様のエラーがでていました。

        UIView.animateWithDuration(1.0,
            delay: 0.0,
            options: UIViewAnimationOptions.CurveEaseIn | 
                  UIViewAnimationOptions.Autoreverse,
            animations: {
                // animation
            },
            completion: { finishe in
                // completion
            }
        )

Bit演算できないってどういうこと?と思って調べてみると、UIUserNotificationTypeUIViewAnimationOptions の型がSwift1.2ではRawOptionSetTypeだったのに、Swift 2.0では OptionSetTypeに変更になっていました。 RawOptionSetTypeはBit演算可能(BitwiseOperationsType)ですが、OptionSetTypeは違うので、bit演算でエラーがでていたんですね。

ヘッダーファイルを見ると、Swift1.2まで使われていたRawOptionSetTypeNS_OPTIONSをそのままインポートしたものでしたが、OptionSetTypeはそれを大幅改良したもののようです。

/// Protocol for `NS_OPTIONS` imported from Objective-C
protocol RawOptionSetType : _RawOptionSetType, BitwiseOperationsType, NilLiteralConvertible {
}

上のUIUserNotificationTypeのコードはこう修正したら大丈夫でした。

let types : UIUserNotificationType = 
    [UIUserNotificationType.Badge, 
    UIUserNotificationType.Alert, 
    UIUserNotificationType.Sound]        
let settins : UIUserNotificationSettings = UIUserNotificationSettings(forTypes: types, categories: nil)        
UIApplication.sharedApplication().registerUserNotificationSettings(settins)

また、UIView の animateWithDurationはこんな感じです。

        UIView.animateWithDuration(1.0,
            delay: 0.0,
            options: [UIViewAnimationOptions.CurveEaseIn, UIViewAnimationOptions.Autoreverse],
            animations: {
                // animation
            },
            completion: { finishe in
                // completion
            }
        )

Xcodeには、最新のSwiftに自動コンバートしてくれる機能があります。 このOptionSetTypeに関しては、自動的に変換してくれる場合もあるみたいですが、私のプロジェクトではだめだったので、大量のUIView.animateWithDurationのoptionを手動で修正しました。

OptionSetTypeのよい点

このOptionSetTypeには set的な演算子があらかじめ定義されているので、オプションの扱いが便利になりました。

たとえば、こんな感じの FoodOptionsというOptionSetTypeの構造体を作ってみます。

struct FoodOptions : OptionSetType {
    
    let rawValue: Int
    init(rawValue: Int) { self.rawValue = rawValue }
    
    static let Apple = FoodOptions(rawValue: 1)
    static let Orange = FoodOptions(rawValue: 2)
    static let Banana = FoodOptions(rawValue: 4)
    static let Potato = FoodOptions(rawValue: 8)
    static let Tomato = FoodOptions(rawValue: 16)
    
    static let Fruits : FoodOptions = [Apple,Orange,Banana]
    static let Vegetables : FoodOptions = [Potato,Tomato]
    static let RedOnes : FoodOptions = [Apple,Tomato]
    static let YellowOnes : FoodOptions = [Banana,Potato]

}

Apple、Orange、Banana……といった、今までと同じようなOption値を作ることもできますが、Fruitsなどのように、複数のOptionを含む状態のOptionを定義することもできます。

二つのOptionSetTypeの or演算には、 unionを使います。 同様に、and 演算には、intersect、exclusive orは、exclusiveOrを使います。

let a1 : FoodOptions = FoodOptions.Fruits
let a2 : FoodOptions = FoodOptions.RedOnes
var a3 : FoodOptions = a1.union(a2) // = Apple,Orange,Banana,Tomato
var a4 : FoodOptions = a1.intersect(a2) // = Apple
let a5 : FoodOptions = a1.exclusiveOr(a2) // = Orange,Banana,Tomato

Optionに特定のOption値が入っているかどうかは containsで調べられます。 isSupresetOfやisSubsetOfでは、setの集合の関係性の確認ができます。

a3.contains(FoodOptions.Tomato) // true

a4.isSupersetOf(FoodOptions.RedOnes) // false
a4.isSubsetOf(FoodOptions.RedOnes) // true

Optionに対する追加や削除も可能です。

a3.insert(FoodOptions.Potato)
a4.remove(FoodOptions.Apple)

Swiftでは、Objective-CにくらべてEnumが拡張されて便利になりましたが、このOptionSetTypeもうまく使うといろいろと便利そうですね。 構造体なので、いろいろとメソッドを追加してさらに便利にできるのもよいところです。

これから使いこなしてみようかと思います。