Posted at

列挙型で表現した UITableViewDataSource のセクションを Raw Value の代わりに配列で管理してみたときの印象を眺めてみる

More than 1 year has passed since last update.

先日に 熊谷と繪面がプログラミングコードの内から聴こえてくる声に耳を傾けて楽しむラジオ #7列挙型を UITableDataSource のセクションを管理するのに使う場面を見るけれど、どういう風にするのが良いのだろう というテーマで談笑をして、その中で やっぱり Raw Value を使うよりは配列を使った方が良さそう という話でいったん落ち着きました。

自分でも、そのあとさらに思いを巡らせてみて、やっぱり配列で管理した方が理屈面ではシンプルに収まりそうに思えました。そしてそのとき実装はどんな印象になるのだろう、そう思って少しばかり試してみました。


検討材料として、序列管理を列挙型で行う場合

なお、話の発端には Raw Value でセクションの序列を管理する というのがあるのですけど、ざっくりと紹介すると、次のように Raw 型を持たせた列挙型でセクションを管理してみます。列挙型に セクション数も自動で管理させたいときには こうして最後に count 列挙子を用意しますが、C や Objective-C ではけっこう自然に採られる方法に思います。

enum Section : Int {

case notification
case controlCenter
case general

case count
}

こうすると、列挙子に 上から順番に 0 からの通し番号が振られる ことと、イニシャライザーが自動実装されて Raw Value から列挙子を作ることができるようになる ので、自動的に UITableViewDataSource から受け取るセクション番号を使って、列挙子をインスタンス化できるようになります。このとき、イニシャライザーは失敗可能イニシャライザーなので、強制アンラップやオプショナルバインディングで対応します。

let section = Section(rawValue: section)!

このように、Swift の列挙型は Raw 型を添えるだけで、それに関連する事柄が自動的に実装 されます。これによって実装が楽になり、むしろ円滑・的確にコードが書けるのではないか、というのがそもそもの発端です。


序列管理をコレクションに任せてみる


セクションの定義

まず、セクションの定義ですけれど、これは自作の TableViewDataSource ドメイン内に所属すると思われるので、その入れ子として定義して見ることにします。

extension TableViewDataSource {

enum Section {

case notification
case controlCenter
case general
}
}


セクションとセクション番号番号

実際の実装では、この Section 列挙型に Raw Value を Int で割り当てて、それでセクション番号を管理する手法もよく取られる様子ですけれど、やっぱりここでそうしてしまうと確かに セクションが外の世界を知りすぎている 感が強い印象がしました。

特に UITableView の場合 セクション番号を 0 から順に割り当てて、その順番に表示する という動きがあって、それを前提としたセクション型であればギリギリセーフな気もしますけれど、それでも Raw Value の意味合いというのを探ってみると、やっぱりそれで序列まで表現するのは、なんとなく違和感を覚えます。

特に順番を変えようと思ったときに違和感を覚えるんですよね。どこかに永続化して保存するとかでなければ Raw 値が変わってもそんなに影響はないと思うのですけど、それでもセクションの表示順番を変えるだけで、いったん決めたセクションの Raw Value が動いてしまうのはちょっと怖さを感じます。


セクションの序列

それよりも、セクションの順番は 序列付きのコレクション で管理する方が良さそうという話になって、それはきっとこんな風に表現することになりそうです。

final class TableViewDataSource : NSObject, UITableViewDataSource {

fileprivate var sections: [Section] = [.notification, .controlCenter, .general]
}


序列をコレクションで管理

セクション自体には序列を持たせないで、序列を持ったコレクション、つまり Array で管理してみました。入れた順番が保存されるので、ここに最初のセクションから順番に列挙子を入れていきます。Swift の Array は 0 から始まる Int 型の通し番号で序列が管理されますし、UITableViewDataSource のセクションも 0 から始まる Int 型の通し番号で管理されるので、インデックスが合致するのも都合が良いです。

順番を入れ替えたいときも、列挙型の Raw Value に触れることなく、入れる順番を変えれば良いので 余計なところに影響が及ぶ可能性がほぼなさそう ですし、もし 表示したくないセクションがあったら配列から取り除けば良い だけなのも簡単です。


行は、今回は "とりあえず動かせるため" の実装で(余談的)

UITableViewDataSource を実装するには、各セクションに対応する行を管理する必要がありますけど、とりあえず 今回そこは追求しない ことにします。

extension TableViewDataSource.Section {

var rows: [String] {

switch self {

case .notification:
return ["電話", "カレンダー"]

case .controlCenter:
return ["ロック画面でのアクセス"]

case .general:
return ["情報", "ソフトウェアアップデート", "アクセシビリティ"]
}
}
}

最適な実装が何かという点については、必要になったらまた考えたいと思います。返す値も今回は簡単に、テキストラベル用の文字列を項目の数だけ配列で返すようにしてみています。


項目の関連付け方

とりあえず、ざっくり書きながら思ったことは、行の項目って各セクションに強く紐づくはずだから、こうやって別のプロパティーで実装するよりも、今回であれば列挙子に強く関連づく形で定義できたらいいのかなって思いました。

そうして真っ先に思いついたのが Associated Value ですけど、それだとランタイムでの柔軟性が高すぎるような印象もして、とりあえず見送りました。セクションと行の項目をもっとしっかり関連づけようとしたとき、もしかするとセクションそのものを列挙型よりは構造体で定義した方がきれいにまとまったりするのかなとも思ったりしました。試していないので、どこまで効果的かはわからないですけれど。


型に左右されない序列の持ち方

そんなことを思ってみたとき、もしセクションを列挙型から構造体に変えたとしても、序列付きコレクションでセクションの順番を管理する方法であれば、全体の設計に大きな影響を与えないことに気がつきました。単純に、列挙子の代わりに構造体のインスタンスを入れれば、おおよそそれで済みそうです。


セクションタイトル

セクションタイトルは、きっと専用のプロパティーを用意して表現するのが良いのかなって思います。または CustomStringConvertible を使って表現するのでも、同じくらいに適切なように感じます。

extension TableViewDataSource.Section {

var title: String {

switch self {

case .notification:
return "通知"

case .controlCenter:
return "コントロールセンター"

case .general:
return "一般"
}
}
}

余談ですけど、セクションを列挙型で定義した場合は、こういうところで網羅性を言語が担保してくれるのが嬉しいところですね。構造体の場合は、構造体自体が title プロパティーなどを持てばより密接にセクション名を定義できるようになりそうで、もしかしてやっぱり列挙型より構造体でセクションを表現する方が向いているのかなっていう思いが強まってきました。


UITableViewDataSource の実装

さて、これでひととおり準備が整ったので UITableViewDataSource の必要なメソッドを実装して、印象を眺めてみることにします。着目点としては セクションを Raw Value で管理する場合と比べて、序列付きコレクションで管理した場合、意味的な観点でも実装的な観点でも、コードの複雑さはどう変化するか です。


セクション数の取得

    func numberOfSections(in tableView: UITableView) -> Int {

return sections.count
}
}

序列付きコレクションであれば、その個数を count プロパティーで取得できるので、それを個数として返しています。列挙型で表現する場合よりも、個数の管理はコレクションの方が圧倒的に得意そうです。


セクションヘッダーの取得

extension TableViewDataSource {

func tableView(_ tableView: UITableView, titleForHeaderInSection index: Int) -> String? {

return sections[index].title
}
}

渡されてきたセクション番号からタイトル文字列を返す処理ですけど、受け取った番号を、そのまま添字に渡して、該当するセクションを表現する列挙子を取得できます。今回は列挙型に、タイトルを表現するプロパティーを定義したので、その値を戻り値として返しています。


セクション毎の行数の取得とセルの取得(余談的)

ここの実装は 今回はとりあえず備えただけなので、今回の実装にあったコードを軽く紹介する程度に留めます。行の管理の仕方で大きく変わってくるはずです。何れにしても、少なくともセクション番号からセクションを取り出す処理は、上記同様、シンプルに書けると思います。

extension TableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection index: Int) -> Int {

return sections[index].rows.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let section = sections[indexPath.section]
let rowTitle = section.rows[indexPath.row]

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!

cell.textLabel?.text = rowTitle

return cell
}
}


全体の印象

こんな感じで、セクションを列挙型で管理して、その際に序列を Raw Value を使わずに表現する方法について思いを巡らせてみました。

序列付きコレクションで管理してみた印象としては、主観ですけど、Raw Value で序列を表現しようとするより コードが明瞭になる気がする のと、それぞれの 役目がはっきりとして考えやすい のと、どこかを 変更しようとしたときの影響範囲が小さくて済む ような印象がしました。

明瞭さについては、やっぱり Raw Value で表現した場合は汎用性が目立ってコードが読みにくくなりがちな印象がしました。額面だけだと Raw Value がセクション番号として使われていることが読み取れない のと、列挙子のインスタンスを作るときに 失敗可能イニシャライザーを使うため nil の可能性を気にしたコードを書かないといけない ところも必要以上に冗長な印象です。Swift は特に コードに意味を持たせやすい 言語に思うので、なおさらそれを感じるのかもしれません。

楽さについても、ともすると列挙型がいろいろ面倒を見てくれる Raw Value を使って組み立てるよりも最終的な手間が小さくて済みそう にも思えましたけど、どんな印象を感じたでしょう。


おわりに

今回はひとまず、セクションだけに着目して見てきましたけど、こんなあたりを参考にしつつ、最良の手を探してみてもらえたら嬉しいです。ともすると構造体でセクションを表現した方がスマートになりそうな気もするので、そんなあたりも追求してみたりすると楽しくなるかもしれません。