Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

enum型の中身にStringを使う

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

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

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

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

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

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

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

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

Swiftではenum型がより使いやすくなり、メソッドが追加できるようになりました。
[補足]コメント欄より:プロパティに関しては、通常のプロパティ(stored property)は追加できませんが、computed propertyなら定義できます。

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

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 と呼びます。関数型言語を使ったことある人には馴染みあるかもしれませんが、OCamlではヴァリアントと呼ばれます。

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

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

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

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

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

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

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 文を書くのはめんどくさいので、メソッド化して値を取り出すようにしましょう。

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な型を格納することもできます。)

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

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

おまけ:Result パターン

enum型の応用として、Result パターンもよく使います。
Resultパターンは結果を表すデータを表現できます。結果は成功の場合と失敗の場合のいずれかを表します。

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

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

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

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

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

// 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)
    }
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away