Swiftで簡単なリファクタリングをしてみよう〜アラート編〜

## はじめに

Swiftでメッセージを表示するには `UIAlertController` というクラスを使います。
使い方が若干難しいので、Swift特有の書き方も交えつつ、リファクタリングしてみます。

## 環境

- Swift：4.2

## 処理の内容

ViewControllerにあるButtonをタップすると以下のメッセージを表示します。

<img width="260" alt="スクリーンショット 2018-12-09 22.07.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/c4edfe39-a75b-9d3b-00ab-72911b982525.png">

## リファクタリング前

リファクタリング前のViewControllerです。
おそらくSwiftに慣れている方からすれば、明らかに冗長だとわかると思います。

```swift:ViewController.swift
import Foundation
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
        let alert: UIAlertController = UIAlertController.init(title: "タイトル", message: "メッセージ",
                                                              preferredStyle: UIAlertController.Style.alert)
        let cancelAction: UIAlertAction = UIAlertAction.init(title: "キャンセル", style: UIAlertAction.Style.cancel,
                                                             handler: { (UIAlertAction) in
                                                                 print("キャンセルが選択されました。")
        })
        alert.addAction(cancelAction)
        let okAction: UIAlertAction = UIAlertAction.init(title: "OK", style: UIAlertAction.Style.default,
                                                         handler: { (UIAlertAction) in
                                                             print("OKが選択されました。")
        })
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }

}
```

## リファクタリングをしてみよう

さっそく1つずつリファクタリングしていきます。

### ①不要なインポートを削除する

UIKitをインポートする場合、Foundationをインポートする必要はありません。

```diff_swift:ViewController.swift
- import Foundation
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
        let alert: UIAlertController = UIAlertController.init(title: "タイトル", message: "メッセージ",
                                                              preferredStyle: UIAlertController.Style.alert)
        let cancelAction: UIAlertAction = UIAlertAction.init(title: "キャンセル", style: UIAlertAction.Style.cancel,
                                                             handler: { (UIAlertAction) in
                                                                 print("キャンセルが選択されました。")
        })
        alert.addAction(cancelAction)
        let okAction: UIAlertAction = UIAlertAction.init(title: "OK", style: UIAlertAction.Style.default,
                                                         handler: { (UIAlertAction) in
                                                             print("OKが選択されました。")
        })
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }

}
```

逆にAPIコールやDBアクセスの実装など、UIを伴わないクラスでUIKitをインポートするのは冗長なので、Foundationのみをインポートするといいです。

### ②イニシャライザをクラス名のみで呼び出す

Swiftではイニシャライザを `{クラス名}.init()` でなく `{クラス名}()` のみで呼び出せます。

```diff_swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
-       let alert: UIAlertController = UIAlertController.init(title: "タイトル", message: "メッセージ",
+       let alert: UIAlertController = UIAlertController(title: "タイトル", message: "メッセージ",
                                                              preferredStyle: UIAlertController.Style.alert)
-       let cancelAction: UIAlertAction = UIAlertAction.init(title: "キャンセル", style: UIAlertAction.Style.cancel,
+       let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel,
                                                             handler: { (UIAlertAction) in
                                                                 print("キャンセルが選択されました。")
        })
        alert.addAction(cancelAction)
-       let okAction: UIAlertAction = UIAlertAction.init(title: "OK", style: UIAlertAction.Style.default,
+       let okAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default,
                                                         handler: { (UIAlertAction) in
                                                             print("OKが選択されました。")
        })
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }

}
```

### ③できる限り型推論する

右辺から型を推論できる場合、型アノテーションを省略することでコードがすっきりして可読性が上がります。

今回だと `UIAlertController` と `UIAlertAction` のインスタンス生成時にイニシャライザを呼び出していて型が明確になっているので、わざわざ型を指定する必要がありません。

```diff_swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
-       let alert: UIAlertController = UIAlertController(title: "タイトル", message: "メッセージ",
+       let alert = UIAlertController(title: "タイトル", message: "メッセージ",
                                                         preferredStyle: UIAlertController.Style.alert)
-       let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel,
+       let cancelAction = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel,
                                                        handler: { (UIAlertAction) in
                                                            print("キャンセルが選択されました。")
        })
        alert.addAction(cancelAction)
-       let okAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default,
+       let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default,
                                                    handler: { (UIAlertAction) in
                                                        print("OKが選択されました。")
        })
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }

}
```

### ④列挙型は `.` から書き始める

Swiftでは列挙型の型が明確な場合、型名を省略して `.` から書き始めることができます。

今回だと `UIAlertController` と `UIAlertAction` のstyleは型が明確なので、型名を省略できます。

```diff_swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
        let alert = UIAlertController(title: "タイトル", message: "メッセージ",
-                                                        preferredStyle: UIAlertController.Style.alert)
+                                                        preferredStyle: .alert)
-       let cancelAction = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel,
+       let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel,
                                                        handler: { (UIAlertAction) in
                                                            print("キャンセルが選択されました。")
        })
        alert.addAction(cancelAction)
-       let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default,
+       let okAction = UIAlertAction(title: "OK", style: .default,
                                                    handler: { (UIAlertAction) in
                                                        print("OKが選択されました。")
        })
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }

}
```

### ⑤トレイリングクロージャを使う

メソッドの最後の引数がクロージャの場合、引数名を省略してカッコの外に出すことができます。
これを __トレイリングクロージャ__ といいます。

可読性が上がるので積極的に取り入れるべきです。

今回だと `UIAlertAction ` のイニシャライザの最後の引数である `handler` はクロージャを引数に取るので外に出せます。

```diff_swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
        let alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert)
-       let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel,
-                                                       handler: { (UIAlertAction) in
-                                                           print("キャンセルが選択されました。")
-       })
+       let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { (UIAlertAction) in
+           print("キャンセルが選択されました。")
+       }
        alert.addAction(cancelAction)
-       let okAction = UIAlertAction(title: "OK", style: .default,
-                                                   handler: { (UIAlertAction) in
-                                                       print("OKが選択されました。")
-       })
+       let okAction = UIAlertAction(title: "OK", style: .default) { (UIAlertAction) in
+           print("OKが選択されました。")
+       }
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }

}
```

### ⑥クロージャで未使用の引数名を省略する

クロージャで使っていない引数は `_` にして省略します。
省略しない場合、SwiftLintでは `unused_closure_parameter` の警告となります。

今回だと `(UIAlertAction)` は使っていないので省略します。

```diff_swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
        let alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert)
-       let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { (UIAlertAction) in
+       let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { _ in
            print("キャンセルが選択されました。")
        }
        alert.addAction(cancelAction)
-       let okAction = UIAlertAction(title: "OK", style: .default) { (UIAlertAction) in
+       let okAction = UIAlertAction(title: "OK", style: .default) { _ in
            print("OKが選択されました。")
        }
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }

}
```

### ⑦引数が少ないオーバーロードメソッドを使う

オーバーロードされているメソッドの場合、できる限り引数が少ないメソッドを使うとコードがすっきりします。

今回だと `present` メソッドの `completion` に `nil` を渡していますが、そもそも `completion` の引数を取らないメソッドが用意されているので、そちらを使います。

```diff_swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
        let alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { _ in
            print("キャンセルが選択されました。")
        }
        alert.addAction(cancelAction)
        let okAction = UIAlertAction(title: "OK", style: .default) { (UIAlertAction) in
            print("OKが選択されました。")
        }
        alert.addAction(okAction)
-       present(alert, animated: true, completion: nil)
+       present(alert, animated: true)
    }

}
```

### ⑧一連の処理をメソッド化する

見てわかる通り、メッセージを表示するだけでかなりの行数を使います。
メッセージを多数呼び出す場合は可読性が著しく落ちるので、メソッド化して簡単に呼び出せるようにします。

```diff_swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
-       let alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { _ in
            print("キャンセルが選択されました。")
        }
-       alert.addAction(cancelAction)
        let okAction = UIAlertAction(title: "OK", style: .default) { _ in
            print("OKが選択されました。")
        }
-       alert.addAction(okAction)
-       present(alert, animated: true)
+       showAlert(title: "タイトル", message: "メッセージ", actions: [cancelAction, okAction])
    }

+   // MARK: Private Methods
+   
+   private func showAlert(title: String, message: String, actions: [UIAlertAction]) {
+       let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
+       actions.forEach { alert.addAction($0) }
+       present(alert, animated: true)
+   }
+
}
```

`UIAlertAction` を配列で渡しているので、メッセージの選択肢を3つ以上にすることもできます。

### ⑨メソッド化した処理を他からも呼び出せるようにする

アラートを表示する処理は他のViewControllerでも使いたいため、 `UIViewController` を拡張してメソッド化した処理を移します。

```diff_swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { _ in
            print("キャンセルが選択されました。")
        }
        let okAction = UIAlertAction(title: "OK", style: .default) { _ in
            print("OKが選択されました。")
        }
        showAlert(title: "タイトル", message: "メッセージ", actions: [cancelAction, okAction])
    }

-   // MARK: Private Methods
-   
-   private func showAlert(title: String, message: String, actions: [UIAlertAction]) {
-       let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
-       actions.forEach { alert.addAction($0) }
-       present(alert, animated: true)
-   }
-
}
```

```diff_swift:UIViewController+Alert.swift
+ import UIKit
+ 
+ /// UIViewController拡張(アラート)
+ public extension UIViewController {
+ 
+     // MARK: Public Methods
+ 
+     func showAlert(title: String, message: String, actions: [UIAlertAction]) {
+         let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
+         actions.forEach { alert.addAction($0) }
+         present(alert, animated: true)
+     }
+ 
+ }
```

## リファクタリング後

これで完成です！
リファクタリング前と比べてコード量が減り、読みやすくなっていることがわかります。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    // MARK: IBActions

    @IBAction func didTapButton(_ sender: UIButton) {
        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { _ in
            print("キャンセルが選択されました。")
        }
        let okAction = UIAlertAction(title: "OK", style: .default) { _ in
            print("OKが選択されました。")
        }
        showAlert(title: "タイトル", message: "メッセージ", actions: [cancelAction, okAction])
    }
```

```swift:UIViewController+Alert.swift
import UIKit

/// UIViewController拡張(アラート)
public extension UIViewController {

    // MARK: Public Methods

    func showAlert(title: String, message: String, actions: [UIAlertAction]) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        actions.forEach { alert.addAction($0) }
        present(alert, animated: true)
    }

}
```

## おわりに

いかがでしたでしょうか？

みなさんも他にいいリファクタリング案がありましたら、どんどんコメントなどでご指摘ください！