SwiftのenumがObjective Cで欲しいときはどうするか
Swiftではenumという種類のデータ型を定義できます。Cのenumと違って、引数を持てるようになっています。これが便利なのは
- ある状態では有効だけど、別の状態では有効ではない値を定義するとき
です(すごい雑な説明なので、他の使い方も調べておきましょう)。
Swiftのenumとは
例えばあなたが最強のレジアプリを開発しているとしましょう。販売する商品の価格には次の二通りがあります。
- 単価と税率が決まっているもの(単価¥100、税8%)
- 全体の金額からの割合であるようなもの(10%割引)
enumを使わないで普通のクラスにしようとすると、こんな感じになるはずです。
class MenuItem { let isPercentage: Bool let unitPrice: NSDecimalNumber! let vatPercentage: NSDecimalNumber! let percentage: Int! }
isPercentage
が偽のときには、unitPrice
とvatPercentage
が設定されて、percentage
がnil
になります。isPercentage
が真のときはその逆。optionalがたくさんあって嫌な感じがしますし、いつか普通に間違えそうです。うっかりisPercentage
な商品のunitPrice
にアクセスしてエラーになるかもしれませんし、それを見た新人がisPercentage
なのにunitPrice
に0を設定するみたいな間違いを犯すかもしれません。少しでも状況を改善するためにはvalidationのコードを書くこともできますが、退屈なのでできるだけやりたくないですね。
こういうことを防ぐにはenumを使います。
enum MenuItemPrice { case Unit(price: NSDecimalNumber, vatPercentage: NSDecimalNumber) case Percentage(percentage: Int) } class MenuItem { let price: MenuItemPrice }
optionalがなくなってやばそうな感じはなくなり、割引の項目の価格にはprice
がないので間違えてアクセスしようとするとコンパイラが教えてくれます。安心。price
の中身にアクセスしようとすると、必ずswitch
が必要になって少し面倒ですが、諦めましょう。
switch price { case Unit(let p): ... case Percentage(let p): ... }
ちなみにswitchを使うと、caseが網羅されているかもコンパイラがチェックしてくれるので、後で価格の種類が増えたときにも安心です。
Objective Cでどうするか
残った問題は一つだけで、2010年から開発されているあなたの最強のレジアプリはObjective Cで書かれているので、こういう良い感じのenumをSwiftで定義してもそれをアプリケーションから使うことは出来ないということです。関連するコードだけでもSwiftで書き直せれば良いのですが、いろいろな事情でそうもいかないことは多いでしょう。
仕方がないのでこうします。
@interface MenuItemPrice : NSObject @end @interface UnitPrice : MenuItemPrice @property (nonatomic) NSDecimalNumber *price; @property (nonatomic) NSDecimalNumber *vatPercentage; @end @interface PercentagePrice : MenuItemPrice @property (nonatomic) NSInteger percentage; @end @interface MenuItem @property (nonatomic) MenuItemPrice *price; @end
これで「単価が決まっている価格なのにうっかりpercentage
にアクセスしてしまう」ことは防げるようになりました。
こうやって定義した値を使う方法はいくつかあります。
MenuItemPrice
にメソッドを追加してサブクラスでオーバーライドするisKindOfClass
して場合分けする
必要なメソッドがわかっている場合は、1.の方法が良いでしょう。例えば「価格を文字列の表現に変換する」みたいな操作は、それでいいと思います。私の場合は、さっさと諦めて、isKindOfClass
で場合分けすることが多いですね。
if ([menuItem.price isKindOfClass:[UnitPrice class]]) {
UnitPrice *price = (UnitPrice *)menuItem.price;
...
}
isKindOfClass
のような操作はオブジェクト指向プログラミングでは避けるべきとされていますが、ここでは別にOOしたいわけではないので良いことにします。キャストが少し面倒なので、ヘルパーを足すことがあります。
@interface MenuItemPrice - (void)asUnitPrice:(void (^)(UnitPrice *))block; - (void)asPercentagedPrice:(void (^)(PercentagedPrice *))block; - (void)switchWithUnitPriceCase:(void (^)(UnitPrice *))unitCase percentagedPriceCase:(void (^)(PercentagedPrice *))percentagedCase; @end
もしも価格の種類が増えた場合にコンパイルエラーになるようにするため、switchWithUnitPriceCase:percentagedPriceCase:
も入れてみました。
まとめ
Swiftのenumって要するに代数的データ型 (Algebraic data type / ADT) なので、Objective Cに欲しい場合は、どうしてもOOPLでADTしたい場合のパターンで逃げることができます。