Swift

SwiftのExtensionによるクラス分割

More than 1 year has passed since last update.

やっている人には何を今更と言われそうですが、しばらく使ってみて思ったことが幾つかあったのでまとめてみます。

間違い、意見、感想、自分はこんなふうに使ってるよ!等ありましたらぜひコメントお願いします。

使用しているXcodeは8.3.3です。


どんなふうに使うの?

一つのクラスをExtensionを使って複数に分けて書きます。自分は大体次の基準で分けます。

あくまで大体の基準で、一つのExtensionが長くなってきたなとか思えば分けることもあります。そこは柔軟に。


  • プロパティと初期化、ライフサイクル

  • UI関連の処理

  • 各種プロトコルの実装

  • (ボタンなどの)アクション

  • アニメーション

  • 通信/ストレージ系

  • などなど...

例えばVCのコードでは以下の感じ。内容は適当です。


サンプル

import UIKit

// MARK: vars and lifecycle
class ViewController: UIViewController {

@IBOutlet fileprivate weak var tableView: UITableView!

override func viewDidLoad() {
super.viewDidLoad()

self.setupUI()
}
}

// MARK: - UI
extension ViewController {

fileprivate func setupUI() {
self.setupHeader()
self.setupTableView()
}

private func setupHeader() {
self.title = "hoge"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "fuga", style: .plain, target: self, action: #selector(self.didTapRightBarButton(_:)))
}

private func setupTableView() {
self.tableView.dataSource = self
self.tableView.delegate = self
}
}

// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}

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

// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// do something
}
}

// MARK: - actions
extension ViewController {

func didTapRightBarButton(_ sender: UIBarButtonItem) {
// do something
}

@IBAction func didTapButton1(_ sender: UIButton) {
// do something
}

@IBAction func didTapButton2(_ sender: UIButton) {
// do something
}
}



良いと思うこと


見通しがよくなる

コードをパッと見た時にかたまりが分かります。コメントで区切る方法もあると思いますが、個人的にはそれより見た目がきれいだと思います。


関連する処理をまとめられる

UIに関連する処理、デリゲートとそのヘルパーメソッド、など近い処理をまとめて書いておくことで読みやすいコードになります。


privateのスコープが狭くなる

あるメソッドのためのヘルパーメソッドなど、クラス全体に見えなくても良いプロパティ/メソッドを作ることがあると思います。

そんな時にExtensionで分けてprivateをつけておくことで可視性を狭めることができます。


悪いと思うこと


タイプ量が多くなる

分けた分だけextension クラス名を書かなければいけないので若干面倒です。

基本コピペだし、遥かに利点の方が大きいので、気にする程ではないですが。


できたらいいなと思うこと


stored propertyの使用

Extension内ではstored propertyを定義することはできません。

computed propertyなら定義することができるので、毎回計算されても良いものはそちらを使います。

状態を保持しておきたいなんてときは、諦めてクラス定義のところにプロパティを書きます。

とあるExtensionでしか使わないプロパティがちょいちょい出てくるので、定義できるようにならないものでしょうか。


IBOutlet/IBActionを直接接続

IBからcontrol+ドラッグで線を引っ張ってきても、Extension内では反応してくれません。

コードの方で@IBAction func didTapButton1(_ sender: UIButton) { ...みたいなのを手書きして、保存した時に現れる丸をIBへ引っ張れば接続することができます。

若干の手間なので、Extensionに引っ張れるようにならないものでしょうか。

1.png

2.png


プロトコルを実装する場合の場所の制限

どこかのExtensionでプロトコルの実装を宣言しても、あくまでそのクラスがプロトコルを実装するということを宣言しただけです。

つまり、宣言したものとは別のExtensionで実装することもできます。


こんなことができる

// プロトコルの実装は宣言するけど中身はなし

extension ViewController: UITableViewDataSource {}

// こちらでUITableViewDataSourceを実装する
extension ViewController {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}

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


まあ書く人が気をつけましょうという話ですが、宣言した場所で実装もしなければならない、とするのは難しいのでしょうか。


おわりに

同じものをまとめる、違うものを分ける、と言うのは設計の基本だと思います。

MV~などと比べれば小さな話かもしれませんが、こういうちょっとしたことでコードの読みやすさは変わると思うので、より良い方法を模索して行きたいです。