【Swift】Protocolごとにextensionで切り分けて実装するワケ

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんぬづは、知人とrebuild.fmの影響で、漫画『ちはやふる』を読み始めて諦めの言葉は青春すべて懸けてから言いたくなった田中です。
ちはやふるはアツい。

さてそれでは本題に入りましょう。

Protocolを切り分けて記述する方法

Swiftでコードを書いているとこんな書き方をよく目にすることはないでしょうか。

extensionで切り分けられたコード

// ViewController.swift

class ViewController: UIViewController {

    var items: [Item]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

extension ViewController: UITableViewDataSource {
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items?.count ?? 0
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCellWithIdentifier("Cell") else {
            fatalError("Empty Cell")
        }
        
        if let items = items {
            cell.textLabel?.text = items[indexPath.row].name
        }
        
        return cell
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        print(items?[indexPath.row].name)
    }
}

なんかextension実装してあるのはわかるけど、でもこれって以下のようにViewControllerにまんま書いても良くない?とも思うワケです。

ViewControllerにベタ書きされたコード

// ViewController.swift

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var items: [Item]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK: - UITableViewDataSource
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items?.count ?? 0
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCellWithIdentifier("Cell") else {
            fatalError("Empty Cell")
        }
        
        if let items = items {
            cell.textLabel?.text = items[indexPath.row].name
        }
        
        return cell
    }
    
    // MARK: - UITableViewDelegate
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        print(items?[indexPath.row].name)
    }
    
}

動きとしては同じですがここにどんな差異があるのか、なぜこんな書き方をするのかについて今日は考えてみました。

理由①:明示的に別処理であることを表すため

extensionとして切り分けることによってViewController内にすべてを詰め込むのとは違い、「それぞれのプロトコルの処理である」という意図がより強く表れるように思います。
「そういう意図を表現するため」というだけで単純に切り分けるのでも、好き好きかもしれませんが整理されて見通しがよくなると感じるので個人的にはアリだと思います。
(好き好きと言ったのは、「1000行とか1500行あるいはそれ以上のレベルの単位でも一つがまとまっている方が読みやすい」という話を人から聞いたことがあるため。読み手の経験に応じてここは変わってくるから)

理由②:一つのソースファイルの肥大化を避ける

ここも先述した通り好き好きの話もあるかとは思いますが、一つのソースファイルが長くなってくると色々な弊害があります。

  • 画面のスクロールに時間がかかる
  • ソースの見通しが悪い

これがextensionによる実装だと単純にファイルの切り分けがしやすかったりします。

ファイル分けしてextension定義した例

// ViewController.swift

class ViewController: UIViewController {

    var items: [Item]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
// ViewController+UITableViewDataSource.swift

import UIKit

extension ViewController: UITableViewDataSource {
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items?.count ?? 0
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCellWithIdentifier("Cell") else {
            fatalError("Empty Cell")
        }
        
        if let items = items {
            cell.textLabel?.text = items[indexPath.row].name
        }
        
        return cell
    }
}
// ViewController+UITableViewDelegate.swift

import UIKit

extension ViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        print(items?[indexPath.row].name)
    }
}

切り分けの粒度は人それぞれと思いますが、個人的にはできるだけ小さく分かれているのが好きです。
とはいえこの程度の実装量だと、一つのファイルにまとめておいた方が良いようにも見えますしそれはその時々によるでしょう。

まとめ

Swiftで書くようになってから、Objective-C時代よりコードの意図をより考える感覚がついてきました。
(自分がObjective-C不慣れなのもあると思うけれど)
表現力が豊かな分、これまでよりさらにプロジェクトごとに整った文脈が必要になってきたように感じます。
「動けば良い」以上に、日常の言葉で文章を書くことと同じ感覚がプログラミングにおいても意味を持つようになってきたんじゃないかなあと、最近は感じています。
楽しい!✌ ('ω' ✌ )三 ✌ ('ω') ✌ 三( ✌ 'ω') ✌