Edited at

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


はじめに

Swiftでメッセージを表示するには UIAlertController というクラスを使います。

使い方が若干難しいので、Swift特有の書き方も交えつつ、リファクタリングしてみます。


環境


  • Swift:4.2


処理の内容

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


リファクタリング前

リファクタリング前のViewControllerです。

おそらくSwiftに慣れている方からすれば、明らかに冗長だとわかると思います。


ViewController.swift

import Foundation

import UIKit

class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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をインポートする必要はありません。


ViewController.swift

- import Foundation

import UIKit

class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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() でなく {クラス名}() のみで呼び出せます。


ViewController.swift

import UIKit


class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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)
}

}



③できる限り型推論する

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

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


ViewController.swift

import UIKit


class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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では列挙型の型が明確な場合、型名を省略して . から書き始めることができます。

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


ViewController.swift

import UIKit


class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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 はクロージャを引数に取るので外に出せます。


ViewController.swift

import UIKit


class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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) は使っていないので省略します。


ViewController.swift

import UIKit


class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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 メソッドの completionnil を渡していますが、そもそも completion の引数を取らないメソッドが用意されているので、そちらを使います。


ViewController.swift

import UIKit


class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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)
}

}



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

見てわかる通り、メッセージを表示するだけでかなりの行数を使います。

メッセージを多数呼び出す場合は可読性が著しく落ちるので、メソッド化して簡単に呼び出せるようにします。


ViewController.swift

import UIKit


class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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 を拡張してメソッド化した処理を移します。


ViewController.swift

import UIKit


class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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)
- }
-
}



UIViewController+Alert.swift

+ import UIKit

+
+ /// UIViewController拡張(アラート)
+ public extension UIViewController {
+
+ // MARK: Public Methods
+
+ /// アラートを表示する
+ ///
+ /// - Parameters:
+ /// - title: タイトル
+ /// - message: メッセージ
+ /// - actions: アラートアクション
+ public 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)
+ }
+
+ }


リファクタリング後

これで完成です!

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


ViewController.swift

import UIKit

class ViewController: UIViewController {

// MARK: IBActions

/// ボタンのタップ
///
/// - Parameter sender: 送り元
@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])
}



UIViewController+Alert.swift

import UIKit

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

// MARK: Public Methods

/// アラートを表示する
///
/// - Parameters:
/// - title: タイトル
/// - message: メッセージ
/// - actions: アラートアクション
public 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)
}

}



おわりに

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

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