LoginSignup
7
7

More than 5 years have passed since last update.

Swiftで文字列または数値のみを許容する型を作ってDictionaryのキーとして使う

Last updated at Posted at 2016-03-08

JSONRPCKitという、SwiftでJSON-RPC 2.0を扱うライブラリを作っているときに必要になったので作ってみました。

文字列または数値のみを許容する型を作る

JSON-RPC 2.0のidは文字列か数値をとる仕様なので、その制限を型で表現してみました。以下のようにenumを使うと簡単です。

public enum RequestIdentifier {
    case NumberIdentifier(Int)
    case StringIdentifier(String)
}

使うときにはJSON、つまり[String: AnyObject]との相互変換が必要になるので、便利メソッドをいくつか用意しておくと使いやすくなります。

extension RequestIdentifier {

    public init?(value: AnyObject) {
        switch value {
        case let number as Int:
            self = .NumberIdentifier(number)
        case let string as String:
            self = .StringIdentifier(string)
        default:
            return nil
        }
    }

    public var value: AnyObject {
        switch self {
        case NumberIdentifier(let number):
            return number
        case StringIdentifier(let string):
            return string
        }
    }
}

RequestIdentifierからAnyObjectへ変換

let id = RequestIdentifier.NumberIdentifier(1)
let json: [String: AnyObject] = ["id": id.value]
debugPrint(json) // ["id": 1]

AnyObjectからRequestIdentifierへ変換

if let id = json["id"].flatMap(RequestIdentifier.init) {
    debugPrint(id) // RequestIdentifier.NumberIdentifier(1)
}

Dictionaryのキーとして使えるようにする

ライブラリ内部でこの型をDictionaryのキーとして使いたかったので、Hashableプロトコルに準拠させました。ハッシュ値として、数値の場合はその値を、文字列の場合はhashValueを返すようにしてみました。

extension RequestIdentifier: Hashable {

    public var hashValue: Int {
        switch self {
        case NumberIdentifier(let number):
            return number
        case StringIdentifier(let string):
            return string.hashValue
        }
    }
}

HashableEquatableを継承しているので、これにも準拠させる必要があります。ハッシュ値が同じときの同値判断のためですね。if case letで2つの値を取り出しながら比較する形にしてみました。

public func ==(lhs: RequestIdentifier, rhs: RequestIdentifier) -> Bool {
    if case let (.NumberIdentifier(left), .NumberIdentifier(right)) = (lhs, rhs) {
        return left == right
    }

    if case let (.StringIdentifier(left), .StringIdentifier(right)) = (lhs, rhs) {
        return left == right
    }

    return false
}

ここまでやると、以下のようにDictionaryのキーとして直接使えるようになります。

var array: [RequestIdentifier: String] = [:]
array[RequestIdentifier.StringIdentifier("a")] = "string key"
array[RequestIdentifier.NumberIdentifier(1)] = "number key"
debugPrint(array) // [RequestIdentifier.StringIdentifier("a"): "string key", RequestIdentifier.NumberIdentifier(1): "number key"]

ちなみに、同じenumでもAssociated Valuesを持たない場合はそのままDictionaryのキーとして使えるようです。

参考

https://github.com/bricklife/JSONRPCKit/blob/master/JSONRPCKit/RequestIdentifier.swift
http://qiita.com/kishisuke/items/c803b1ce2bbc91e5b278
https://ez-net.jp/article/5D/T5i2IddA/E6FDjcBSY6e1/

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7