この記事は 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 になっている場合は、JsonDecoder
に keyDecodingStrategy
を指定することで 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:)
をつかう
SubjectType
は CaseStringConvertible
というユーザー定義のプロトコルに適合していました。これは、 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 を使ってたのしくコーディングしていきましょう! 🎉