iOSアプリの基本設計を考える：「UIViewController」画面遷移（２）

クラスの定義にジェネリックスを使って結果を定義し、クロージャで返すという手法は、結果を無視したいUIViewControllerにも影響を与える（し見た目複雑）ためにあまり良くないなと感じたので、シンプルに実装できる方法に変更しました。
元の内容は残しておきます。

##問題

[iOSアプリの基本設計を考える：「UIViewController」画面遷移（１）](https://qiita.com/BlueEventHorizon/items/08ccc5f9139b3b94a9cd)では画面終了のクロージャについて描きました。ただ、このままでは少し困ったことがあります。
その画面、終了したことは分かったけど、結果はなんなの？と言うことです。
　例えば下記のように「キャンセル」ボタンと「完了」ボタンがあった場合は、呼び出しもとは結果を知りたいですよね？

![スクリーンショット 2017-11-15 23.23.34.png](https://qiita-image-store.s3.amazonaws.com/0/37634/8954851f-b728-9225-7aab-fa08064834eb.png)

## Read Only変数で返す

CoreViewControllerはそのままで変更しません。

```swift:Swift
import UIKit

class CoreViewController: UIViewController {

    // 完了クロージャ
    var completion: ((_ viewController: UIViewController, _ animated: Bool) -> Void)? = nil

    // ボタンなどで画面を終了する場合
    func dismissCore(animated: Bool)
    {
        if completion != nil
        {
            completion?(self, animated)
            completion = nil
            print("\(String(describing: type(of: self))) で \(#function) が呼び出されました")
        }
    }
    
    // ナビゲーションバーの戻るボタン "<" で画面を終了する場合
    deinit
    {
        dismissCore(animated: true)
    }
}
```

結果を通知するためのenum型を定義します。
ジェネリックスで記述されているために、Tを取り替えるだけでそのViewController独自の定義を使用することができます。

```swift:Swift
import Foundation

/// 結果通知用定義
///
/// - success       成功の場合　オブジェクトを返す
/// - failure       失敗の場合  Error型を返す
enum Result<T, E:Error>
{
    case success(T)     // .successの場合はT型
    case failure(E)     // .failureの場合はError型
}

```

呼び出し先のViewControllerには下記のような結果を定義し、上記のResultに適用します。

```swift:Swift
// 成功時
enum vcResult : Int
{
    case ok         // 正常終了
    case canceled   // キャンセルされた
}

// エラー時
enum vcErr : Error
{
    case networkErr
    case unknownErr
}

// localizedDescriptionの定義（変更）
extension vcErr: LocalizedError
{
    public var errorDescription: String?
    {
        switch self
        {
        case .networkErr:
            return "ネットワークエラーが発生しました"
        case .unknownErr:
            return "原因不明のエラーが発生しました"
        }
    }
}
```

ViewControllerには、変数として

```swift:Swift
public private(set) var  result: Result<vcResult, vcErr>?
```
を追加します。public private(set) varは、内部のみ設定可能、つまりRead Onlyを意味します。
ViewControllerのコードは下記のようになります。
それぞれOKボタンとキャンセルボタンが押下されたタイミングで単純にresult変数に結果を書き込んでいます。

```swift:Swift
class ViewController: CoreViewController {

    public private(set) var  result: Result<vcResult, vcErr>?
    
    // ストリーボードからインスタンを生成
    static func getInstance() -> ViewController?
    {
        let storyboardInstance = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboardInstance.instantiateViewController(withIdentifier: String(describing: self))
        return vc as? ViewController
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ナビゲーションバーの戻るボタン "<" で画面を終了する場合のResultを指定する
        result = .success(.canceled) // 
    }
    
    // キャンセルボタンが押された
    @IBAction func pushedCancelButton(_ sender: Any) {
        self.result = .success(.canceled)
        dismissCore(animated: true)
    }

    // OKボタンが押された
    @IBAction func pushedOKButton(_ sender: Any) {
        self.result = .success(.ok)
        dismissCore(animated: true)
    }
}
```

呼び出し側の記述です。ちょっと長くなってしまいますが、結果の処理方法も記載しました。
switch文でenum Result<T, E:Error>から必要なパラメータを抜き出しています。
この形式では、ViewControllerの終了クロージャがViewControllerの親クラスであるCoreViewControllerの型で自インスタンスを返してくるので

```swift:Swift
let vc: ViewController? = viewController as? ViewController
```
でキャストして使用しています。

```swift:Swift

import UIKit

class FirstViewController: UIViewController {

    // present(_:animated:completion:)を呼び出す
    @IBAction func pushedPresent(_ sender: Any) {
        
        if let vc = ViewController.getInstance()  // インスタンス生成
        {
            // 終了クロージャの設定
            vc.completion = { viewController, animated in
                
                let vc: ViewController? = viewController as? ViewController
                if let _vc = vc, let _result = _vc.result
                {
                    switch _result
                    {
                    case let .success(success):
                        switch success
                        {
                        case .ok:
                            print("*** ok ***")
                        case .canceled:
                            print("***  canceled ***")
                        }
                        
                    case let .failure(error):
                        print("*** error \(error.localizedDescription) ***")
                        break
                    }
                }
                
                // 画面終了（インスタンスの消滅）
                viewController.dismiss(animated: animated, completion: nil)
                
                print("\(String(describing: type(of: self))) で \(#function) が呼び出されました")
                
            }
            
            // 画面表示
            self.present(vc, animated: true)
        }
    }
    
    // pushViewController(_:animated:)を呼び出す
    @IBAction func pushedPushViewController(_ sender: Any) {
        
        if let vc = ViewController.getInstance()  // インスタンス生成
        {
            // 終了クロージャの設定
            vc.completion = { viewController, animated in
                
                let vc: ViewController? = viewController as? ViewController
                if let _vc = vc, let _result = _vc.result
                {
                    switch _result
                    {
                    case let .success(success):
                        switch success
                        {
                        case .ok:
                            print("*** ok ***")
                        case .canceled:
                            print("***  canceled ***")
                        }
                        
                    case let .failure(error):
                        print("*** error \(error.localizedDescription) ***")
                        break
                    }
                }
                
                // 画面終了（インスタンスの消滅）
                viewController.navigationController?.popViewController(animated: true)
                
                print("\(String(describing: type(of: self))) で \(#function) が呼び出されました")
                
            }

            // 画面表示
            self.navigationController?.pushViewController(vc, animated: true)
        }
    }
}
```

## ソースコード

なおソースコードをGitHubに上げております。

[ソースコードはここです](https://github.com/BlueEventHorizon/SwiftProtoApp.git)


#ここから過去記事（あまり良くないかも知れません）
##ジェネリックス

そこで、「[iOSアプリの基本設計を考える：「UIViewController」画面遷移（１）](https://qiita.com/BlueEventHorizon/items/08ccc5f9139b3b94a9cd)」の完了クロージャに手を入れます。

```swift:Swift
import UIKit

class CoreViewController: UIViewController {

    // 完了クロージャ
    var completion: ((_ viewController: UIViewController, _ animated: Bool) -> Void)? = nil
    
    // ボタンなどで画面を終了する場合
    func dismissCore(animated: Bool)
    {
        completion?(self, animated)
        completion = nil
        print("\(String(describing: type(of: self))) で \(#function) が呼び出されました")
    }
    
    // ナビゲーションバーの戻るボタン "<" で画面を終了する場合
    deinit
    {
        completion?(self, true)
        completion = nil
        print("\(String(describing: type(of: self))) で \(#function) が呼び出されました")
    }
}
```

を

```swift:Swift
import UIKit

/// 結果通知用定義
///
/// - success       成功の場合　オブジェクトを返す
/// - failure       失敗の場合  Error型を返す
enum Result<T, E:Error>
{
    case success(T)     // .successの場合はT型
    case failure(E)     // .failureの場合はError型
}

class CoreViewController<T, E:Error>: UIViewController {

    // 完了クロージャ
    var completion: ((_ viewController: UIViewController, _ result: Result<T, E>, _ animated: Bool) -> Void)? = nil
    // ナビゲーションバーの戻るボタン "<" で画面を終了する場合のResultを指定する
    var result: Result<T, E>? = nil

    // ボタンなどで画面を終了する場合
    func dismissCore(result: Result<T, E>, animated: Bool)
    {
        completion?(self, result, animated)
        completion = nil
        print("\(String(describing: type(of: self))) で \(#function) が呼び出されました")
    }
    
    // ナビゲーションバーの戻るボタン "<" で画面を終了する場合
    deinit
    {
        if let _result = result
        {
            completion?(self, _result, true)
            completion = nil
        }

        print("\(String(describing: type(of: self))) で \(#function) が呼び出されました")
    }
}
```

と変えてみましょう。

```swift:Swift
 enum Result<T, E:Error>
```

```swift:Swift
 class CoreViewController<T, E:Error>: UIViewController
```

というような書き方をジェネリックスと呼びます。
ここではジェネリックスの詳細は述べませんが、これによってCoreViewControllerの子クラスは
自身の定義した結果を完了クロージャを通じて呼び出し元画面に戻すことができるようになります。

子クラスも修正してみます。

```swift:Swift

import UIKit

// 成功時
enum vcResult : Int
{
    case ok
    case canceled
}

// エラー時
enum vcErr : Error
{
    case networkErr
    case unknownErr
}

// localizedDescriptionの定義（変更）
extension vcErr: LocalizedError
{
    public var errorDescription: String?
    {
        switch self
        {
        case .networkErr:
            return "ネットワークエラーが発生しました"
        case .unknownErr:
            return "原因不明のエラーが発生しました"
        }
    }
}

class ViewController: CoreViewController<vcResult, vcErr> {

    // ストリーボードからインスタンを生成
    static func getInstance() -> ViewController?
    {
        let storyboardInstance = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboardInstance.instantiateViewController(withIdentifier: String(describing: self))
        return vc as? ViewController
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ナビゲーションバーの戻るボタン "<" で画面を終了する場合のResultを指定する
        result = .success(.canceled) // 
    }
    
    // キャンセルボタンが押された
    @IBAction func pushedCancelButton(_ sender: Any) {
        dismissCore(result: .success(.canceled), animated: true)
    }

    // OKボタンが押された
    @IBAction func pushedOKButton(_ sender: Any) {
        dismissCore(result: .success(.ok), animated: true)
    }
}

```

これで、
キャンセルボタンが押された場合は、.success(.canceled)を返します。
OKボタンが押された場合は、.success(.ok)を返します。
ナビゲーションバーの戻るボタン "<" で画面を終了する場合は、.success(.canceled)を返します。

## 補足

```swift:Swift

// localizedDescriptionの定義（変更）
extension vcErr: LocalizedError
{
    public var errorDescription: String?
    {
        switch self
        {
        case .networkErr:
            return "ネットワークエラーが発生しました"
        case .unknownErr:
            return "原因不明のエラーが発生しました"
        }
    }
}

```

ここは、エラーの内容をlocalizedDescriptionで取り出すためのextensionです。
LocalizedErrorプロトコルを具備しているところがポイントです。

```swift:Swift
switch result
                {
                case let .success(result):
                    switch result
                    {
                    case .ok:
                        // 処理
                    case .canceled:
                        // 処理
                    }

                case let .failure(error):
                    self.alert(error.localizedDescription, ok: nil, cancel: nil) // 適当・・・
                }
```

というような感じで使います。


## ソースコード

なおソースコードをGitHubに上げております。

[ソースコードはここです](https://github.com/BlueEventHorizon/SwiftProtoApp.git)

以上です。