Swift初心者を卒業するためのTips(enum編)


SwiftはObjective-Cの知識があればある程度書けますが、Swift固有の機能もあります。
今回はそういった機能について紹介し、Objective-C使いがSwift初心者を卒業するためのTipsについて紹介します。

## enum型の中身にStringを使う

Objective-CはC言語の拡張なので、enumの値としてIntegerの値しか扱うことができません。

```objc
typedef NS_ENUM (NSUInteger, kIconSize) {
    kIconSizeSmall,
    kIconSizeMedium,
    kIconSizeLarge,
    kIconSizeOriginal
};
```

SwiftからはInteger以外にもStringなどが扱えます。

```swift
enum kIconSize : String {
    case Small		= "Small"
    case Medium		= "Medium"
    case Large		= "Large"
    case Original	= "Original"
}

// .rawValue で値を取り出す
println(kIconSize.Small.rawValue)
// -> "Small"
```

Swiftのswitch文ではStringの値もマッチさせることができるので、こういう使い方もできます。

```swift
let size = kIconSize.Small
switch size {
case .Small:
	// ここにマッチ
    println("Small Size")	// -> "Small Size"
case .Medium:
    println("Medium Size")
case .Large:
    println("Large Size")
case .Original:
    println("Original Size")
}
```

## enum型にメソッドを追加する

Objective-Cではenum型のデータに関連したメソッドを定義することはできませんでした。その代わりとして、グローバルスコープで以下のような関数を定義していたと思います。

```objc
- (NSString *)iconSizeToString:(kIconSize)iconSize
{
    switch (iconSize) {
        case kIconSizeSmall:
			return @"Small";
        case kIconSizeMedium:
			return @"Medium";
        case kIconSizeLarge:
			return @"Large";
        case kIconSizeOriginal:
        	return @"Original"
     }
}
```

Swiftではenum型がより使いやすくなり、メソッドが追加できるようになりました。
<b>[[補足]コメント欄より](http://qiita.com/gologo13/items/0d484da7d4c9beb9e0b1#comment-e5b4ca4041157446b165)</b>：プロパティに関しては、通常のプロパティ(stored property)は追加できませんが、computed propertyなら定義できます。


これにより、enum型のデータとそれに対する振る舞いをカプセル化することができます。

```swift
enum kIconSize {
    case Small
    case Medium
    case Large
    case Original
    
    func sizeName() -> String {
        switch self {
        case .Small:
            return "Small"
        case .Medium:
            return "Medium"
        case .Large:
            return "Large"
        case .Original:
            return "Original"
        }
    }
}

println(kIconSize.Small.sizeName())
// "Small"
```

## Associated Value

Swiftのenum型は値を格納することができます。この機能を **[Associated Value](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID148)** と呼びます。関数型言語を使ったことある人には馴染みあるかもしれませんが、OCamlでは[ヴァリアント](https://ocaml.org/learn/tutorials/data_types_and_matching.ja.html#_5)と呼ばれます。

以下の例では Barcode という名前のenum型を定義していて、UPCA, QRCodeの２つのCaseがあります。UPCA は Int の値を4つ、QRCode は String の値を1つ格納することができます。

```swift
// 定義
enum Barcode {
	case UPCA(Int, Int, Int, Int)
	case QRCode(String)
}
```

まずBarcode型のデータを生成してみます。
Barcode.QRCodeの場合、初期化時に格納する値にStringを指定します。

```swift
// Barcode型の初期化
let barcode = Barcode.QRCode("ABCDEFGHIJKLMNOP")

// ↓はコンパイルエラーになる
Barcode.QRCode()
```

格納されたデータは switch 文を使って取り出せます。

```swift
switch barcode {
	case .UPCA(let numberSystem, let manufacturer, let product, let check):
		println("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
	case .QRCode(let productCode):
		println("QR code: \(productCode).")
}
// prints "QR code: ABCDEFGHIJKLMNOP."
```

格納された値を取り出すときにいちいち switch 文を書くのはめんどくさいので、メソッド化して値を取り出すようにしましょう。

```swift
enum Barcode {
    case UPCA(Int, Int, Int, Int)
    case QRCode(String)
    
    func qrcode() -> String? {
    	switch self {
    		case QRCode(let qrcode): 
    			return qrcode
    		default:
    			return nil
    	}
    }
}
```

ちなみに、Swiftでよく出てくるOptional型も値を格納できるenum型として定義できます。
（enum型はGenericな型を格納することもできます。）

```swift
enum Optional<T> {
	case Some(T)
	case None
}

var x = Optional<Int>.None  // nil と同じ意味
x = Optional<Int>.Some(10)  // Optional(10) と同じ意味
```

## おまけ：Result<T> パターン

enum型の応用として、[Result<T>](https://github.com/antitypical/Result) パターンもよく使います。
Resultパターンは<b>結果</b>を表すデータを表現できます。結果は成功の場合と失敗の場合のいずれかを表します。

以下がResultパターンの定義です。

```swift
enum Result<T> {
	// 成功の場合
	case Success(T)
	// 失敗の場合
	case Error(NSError)
}
```

Objective-C では非同期な処理の結果を受け取るときに、以下の様な結果とエラーの２引数を受け取るようなcompletion handler を設定していたかと思います。

```objc
- (void)doAsyncRequestWithCompletion:(void (^)(NSDictionary *response, NSError *error))completionHandler;

```

Result パターンを使えば、そのような必要はなくなります。
以下は Result パターンの使用イメージです。

```swift
// T にはレスポンスのモデルクラスを指定する
class HTTPClient<T> {
    
    // 通信が失敗した時のエラー情報を返す
    func error() -> NSError?  { ... }
    
    // 通信が成功した場合のレスポンスを返す
    func getResult() -> T { ... }
    
    // 非同期で結果が返ってくるメソッド
    func getResultAsync(completion:(Result<T> -> Void)) {
        // 処理に失敗した場合
        if let error = self.error() {
            completion(Result<T>.Error(error))
            return
        }
        
        // 成功の場合
        let result = self.getResult()
        completion(Result<T>.Success(result))
    }
}

// 使用例
HTTPClient<UserModel>().getResultAsync { result in
    switch result {
    case .Error(let error):
        println(error)
    case .Success(let resp):
        println(resp)
    }
}
```