【Swift】Enumの実際の使われ方を調べてみた


# はじめに

SwiftのEnumにはObjective-Cのときにはなかった特徴があります。

* Function や Computed Property が持てる。
* 関連値が持てる。
* プロトコルが使える。
* ジェネリクスが使える。

どんな使い方があるか、Cocoapodsの有名ライブラリを見てみました。

# 成功/失敗

成功と失敗の状態をEnumで表しつつ、失敗の方にはエラーオブジェクトを関連値として渡す。

[Alamofire/Validation.swift at 990fded98afe5135dc418e1f6eb0287027dd067f · Alamofire/Alamofire](https://github.com/Alamofire/Alamofire/blob/990fded98afe5135dc418e1f6eb0287027dd067f/Source/Validation.swift)

```swift:Validation.swift
    public enum ValidationResult {
        case Success
        case Failure(NSError)
    }
```


# ErrorTypeプロトコルとの組み合わせ

`throw`で投げられるエラーをEnumで定義する場合は、ErrorTypeプロトコルを付ける。

[realm-cocoa/Error.swift at 7d97479d49ea896859eb3b32d5caf18b5ce1b9ba · realm/realm-cocoa](https://github.com/realm/realm-cocoa/blob/7d97479d49ea896859eb3b32d5caf18b5ce1b9ba/RealmSwift-swift2.0/Error.swift)

```swift:Error.swift
public enum Error: ErrorType {

	...

    /// Error thrown by Realm if no other specific error is returned when a realm is opened.
    case Fail

    /// Error thrown by Realm for any I/O related exception scenarios when a realm is opened.
    case FileAccess

    /// Error thrown by Realm if the user does not have permission to open or create
    /// the specified file in the specified access mode when the realm is opened.
    case FilePermissionDenied

    /// Error thrown by Realm if the file already exists when a copy should be written.
    case FileExists

    /// Error thrown by Realm if no file was found when a realm was opened as
    /// read-only or if the directory part of the specified path was not found
    /// when a copy should be written.
    case FileNotFound

    /// Error thrown by Realm if the database file is currently open in another process which
    /// cannot share with the current process due to an architecture mismatch.
    case IncompatibleLockFile

    /// Returned by RLMRealm if a file format upgrade is required to open the file,
    /// but upgrades were explicilty disabled.
    case FileFormatUpgradeRequired
}
```

# 成功/失敗 + ジェネリクス

成功と失敗の状態をEnumで定義し、関連値の型をジェネリクスで指定する。
下のAlamofireの例では、前項目のErrorTypeプロトコルも使用している。

[Alamofire/Result.swift at 990fded98afe5135dc418e1f6eb0287027dd067f · Alamofire/Alamofire](https://github.com/Alamofire/Alamofire/blob/990fded98afe5135dc418e1f6eb0287027dd067f/Source/Result.swift)

```swift:Result.swift
public enum Result<Value, Error: ErrorType> {
    case Success(Value)
    case Failure(Error)

	...
}
```


# Function/Computed Property

自身がどのEnum値かによって返す値を変える。
ただ、あまり分岐が複雑になるようであれば、プロトコル＋構造体にした方がコードを分離できて良さそう。

[Alamofire/ParameterEncoding.swift at 990fded98afe5135dc418e1f6eb0287027dd067f · Alamofire/Alamofire](https://github.com/Alamofire/Alamofire/blob/990fded98afe5135dc418e1f6eb0287027dd067f/Source/ParameterEncoding.swift)

```
public enum ParameterEncoding {
    case URL
    case URLEncodedInURL
    case JSON
    case PropertyList(NSPropertyListFormat, NSPropertyListWriteOptions)
    case Custom((URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?))

    /**
        Creates a URL request by encoding parameters and applying them onto an existing request.
        - parameter URLRequest: The request to have parameters applied
        - parameter parameters: The parameters to apply
        - returns: A tuple containing the constructed request and the error that occurred during parameter encoding, 
                   if any.
    */
    public func encode(
        URLRequest: URLRequestConvertible,
        parameters: [String: AnyObject]?)
        -> (NSMutableURLRequest, NSError?)
    {
        var mutableURLRequest = URLRequest.URLRequest

        guard let parameters = parameters where !parameters.isEmpty else {
            return (mutableURLRequest, nil)
        }

        var encodingError: NSError? = nil

        switch self {
        case .URL, .URLEncodedInURL:
            func query(parameters: [String: AnyObject]) -> String {
                var components: [(String, String)] = []

                for key in parameters.keys.sort(<) {
                    let value = parameters[key]!
                    components += queryComponents(key, value)
                }

                return (components.map { "\($0)=\($1)" } as [String]).joinWithSeparator("&")
            }

            func encodesParametersInURL(method: Method) -> Bool {
                switch self {
                case .URLEncodedInURL:
                    return true
                default:
                    break
                }

                switch method {
                case .GET, .HEAD, .DELETE:
                    return true
                default:
                    return false
                }
            }

            if let method = Method(rawValue: mutableURLRequest.HTTPMethod) where encodesParametersInURL(method) {
                if let URLComponents = NSURLComponents(URL: mutableURLRequest.URL!, resolvingAgainstBaseURL: false) {
                    let percentEncodedQuery = (URLComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
                    URLComponents.percentEncodedQuery = percentEncodedQuery
                    mutableURLRequest.URL = URLComponents.URL
                }
            } else {
                if mutableURLRequest.valueForHTTPHeaderField("Content-Type") == nil {
                    mutableURLRequest.setValue(
                        "application/x-www-form-urlencoded; charset=utf-8",
                        forHTTPHeaderField: "Content-Type"
                    )
                }

                mutableURLRequest.HTTPBody = query(parameters).dataUsingEncoding(
                    NSUTF8StringEncoding,
                    allowLossyConversion: false
                )
            }
        case .JSON:
            do {
                let options = NSJSONWritingOptions()
                let data = try NSJSONSerialization.dataWithJSONObject(parameters, options: options)

                mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
                mutableURLRequest.HTTPBody = data
            } catch {
                encodingError = error as NSError
            }
        case .PropertyList(let format, let options):
            do {
                let data = try NSPropertyListSerialization.dataWithPropertyList(
                    parameters,
                    format: format,
                    options: options
                )
                mutableURLRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type")
                mutableURLRequest.HTTPBody = data
            } catch {
                encodingError = error as NSError
            }
        case .Custom(let closure):
            (mutableURLRequest, encodingError) = closure(mutableURLRequest, parameters)
        }

        return (mutableURLRequest, encodingError)
    }
```

# おわりに

とりあえず調べた範囲で挙げてみました。
いくつか見てみたのですが、結局Alamofireが多いですね...。
他にも見つけたら適宜追加しようと思います。