LoginSignup
12
5

More than 5 years have passed since last update.

日本語 Codable Enums

Posted at

この記事は iOS Advent Calendar 2018 の 6日目の記事です。

Codable1 は、Swift 4.0 (3.2) で登場し、今年リリースされた Swift 4.1 / 4.2 のアップデートによって、さらに使いやすくなりました。

この記事では、実際に今年iOSアプリ開発で遭遇したケースを元に2、 enum、 Codable など、 Swift らしい機能を使って JSON を簡潔に扱う例を紹介します。

想定ケース

「やるべき宿題(タスク)を表示してくれるリスト」を表示する画面を作ります。

API からは以下のような、タスクの配列が返ってきます。

{
    [
    id: 1,  // タスクID
    subject_type: 2, // 教科タイプ
    books: [ ... ] ... // やるべき本とかとか
    ], ...
}

宿題には、教科のタイプ(国語、英語とか)が含まれます。

これを、リストの各要素に表示する画面を作ります。教科ごとに色が決まっていて、リストの要素の背景や、文字などに使用します。

また、教科のリストを選んで設定画面も作ります。

まとめると以下のようになります。

  • 課題の配列を、リストに表示する画面がある
  • 教科タイプ (subject_type) は、整数型で返ってくる
  • 教科の情報 (教科名, 色など) があり、クライアントで持っている
  • 教科タイプ (subject_type) をユーザーに選んでもらって、 post してもらう画面がある

JSON のパースには Codable を使うとよさそうですね。使ってみましょう。

実装例

以下のような型を定義しました。

struct Task: Codable {
    let id: Int
    let books: [Book]
    let subjectType: SubjectType
}

struct Book: Codable {
    let id: Int
    let title: Int
    ...
}

enum SubjectType: Int, Codable, CaseIterable, CaseStringConvertible {
    case 数学
    case 英語
    case 倫理
    case 簿記会計
    case 生物基礎

    var color: UIColor {
        switch self {
        case .数学: return .blue
        case .英語: return .red
        case .倫理: return .yellow
        case .簿記会計: return .brown
        case .生物基礎: return .green
        }
    }
}

注目してほしいのは SubjectType です。 Codable で、CaseIterable で、 CaseStringConvertible な Int の enum になっています。らぶるらぶる。

subjectType は単なる Int で指定しても実装できてしまうのですが、enum な型にするのには以下のようなメリットがあります。

  • 他の型でも subjecType が登場するとき、それらが同一のものであることを示せる (型にするメリット)
  • switch 文で使うとき、 default: を使わなければ case が増えたときにビルドエラーになり、もれなく列挙できる (enum を使うメリット)

Int の enum は、何も指定しない場合は 0 から値が割り当てられ、 subject_type の値が 0 なら数学、 1 なら英語に map されます。特定の値を特定の case としたいときには数値を指定することもできます。

    case 数学 = 5
    case 英語

ちなみに、JSONのほうのキーが snake_case になっている場合は、JsonDecoderkeyDecodingStrategy を指定することで camelCase に変換してくれます(Swift 4.1〜)。

let jsonDecoder = JsonDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase

これ以外にも、テクニックというと大げさですが、簡潔に扱うために工夫している部分があるので、ここでは3つほど紹介します。

テクニック1: 日本語をつかう

これは早速好みの分かれそうな方法ですが、case の名前に日本語を使っています。

日本語を使うことにはもちろんデメリットもあります。そのため、辞書を使っていい感じの英語に英訳できる場合は、英語を使うことも多いのですが、今回のような例だと…

    case 数学
    case 英語
    case 倫理
    case 簿記会計
    case 生物基礎

律儀に英訳していくのは結構たいへんではないでしょうか?(特に「簿記・会計」とか…)

この場合は、 case の名前に、無理せず日本語をそのまま使うことで、可読性や保守性を上げることができるケースになるのではないかと思います(そして、後述する副次的な効果もあります)。

都道府県名なども日本固有の名前なので、日本語にしてしまうのがおすすめの例です。

enum Prefecture {
    case 北海道
    case 青森県
    case 岩手県
    ...
}

日本語命名は便利ですが使う場所によってはリスクもあり、たとえば Swift4.2 では、 Storyboard に紐付いている ViewController の名前などに指定していると、インスタンス化に失敗することがあります。しかし enum の case 名であれば、まず問題ないと思います。

テクニック2: String(describing:) をつかう

SubjectTypeCaseStringConvertible というユーザー定義のプロトコルに適合していました。これは、 String(describing: self) 3を返すプロトコルです。

protocol CaseStringConvertible { }

extension CaseStringConvertible {
    var string: String {
        return String(describing: self)
    }
}

hoge.string // それ自身を表現する文字列

String(describing: self) は、それ自身を表現する文字列を返してくれます。たとえば、 Int の enum のインスタンスを渡したときは、case の名前になります。print 関数にインスタンスを与えたときに出力される内容は、これと同じです。

case名を日本語にしたいのはこれを使いたいからでもあります。UIの表示名に合わせておくことで、 UIに表示すべき名前を インスタンス名.string で簡潔に取り出すことができます。

subjectLabel.text = task.type.string // 生物基礎 とかが返ってくる
task.type.rawValue // Int そのものの値が欲しいとき

また、色を返す変数を用意していたので、同じように .color でシンプルに取り出すことができます。

    var color: UIColor {
        switch self {
        case .数学: return .blue
        case .英語: return .red
        case .倫理: return .yellow
        case .簿記会計: return .brown
        case .生物基礎: return .green
        }
    }
}
...

cell.subjectView.backgroundColor = task.type.color

これでリストの要素を簡潔に表現することができそうです。

テクニック3: CaseIterable をつかう

Swift 4.2 で、CaseIterable というプロトコルが増えました4。これに適合させた enum は、 .allCases で case のコレクションが取れるようになりました。

public protocol CaseIterable {
    /// A type that can represent a collection of all values of this type.
    associatedtype AllCases : Collection where Self.AllCases.Element == Self

    /// A collection of all values of this type.
    public static var allCases: Self.AllCases { get }
}

たとえばユーザーに SubjectType を選択してもらうリストを作りたい場合、 .allCases を使って表示するべき内容を指定することができます。

func collectionView(_ collectionView: UICollectionView,
                    cellForItemAt indexPath: IndexPath)
    -> UICollectionViewCell {
    let cell = collectionView
        .dequeueReusableCell(with: MyCell.self, for: indexPath)
    cell.setUp(title: SubjectType.allCases[indexPath.row].string)
}

func collectionView(_ collectionView: UICollectionView,
                    didSelectItemAt indexPath: IndexPath) {
    self.selectedType = SubjectType(rawValue: indexPath.row)!
    collectionView.reloadData()
}

便利ですね。

SubjectType は Codable (= JSON から変換できるし、JSON に変換可能)なので、選ばれた教科をそのまま post することができます。これで教科を選択する画面も作れそうです。

なお、 CaseIterable は Swift4.2 で追加されたプロトコルですが、Swift 4.2 以前でも、以下のように自分で定義して使うことができます5

public protocol EnumEnumerable { associatedtype Case = Self }
public extension EnumEnumerable where Case: Hashable {
    private static var iterator: AnyIterator<Case> {
        var n = 0
        return AnyIterator {
            defer { n += 1 }
            let next = withUnsafePointer(to: &n) {
                UnsafeRawPointer($0).assumingMemoryBound(to: Case.self).pointee
            }
            return next.hashValue == n ? next : nil
        }
    }
    public static var allCases: [Case] {
        return Array(self.iterator)
    }
}

まとめ

Codable に適合した Enum を使うことで、 JSON を返す API とのやりとりを簡潔に Swift らしく表現できます。

また、以下と組み合わせることによって、さらに簡潔に表現できる場合があります。

  • case の名前は無理に英訳せず、UI上の表示名を合わせられないか検討する
  • String(describing:) を使う
  • CaseIterable を使う

2019年も enum と Codable を使ってたのしくコーディングしていきましょう! 🎉

12
5
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
12
5