0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

コールバックの悪い例(Swift5)

0
Last updated at Posted at 2019-12-25

はじめに

お久しぶりです
年末に差し掛かり忙殺されてきました

ところでコールバック地獄をご存知ですか?
私もふとした時にはこの地獄に足を踏み入れそうになりますが思い留まろうともがいています
そんな地獄を見ないために悪い例と良い?例を紹介します

準備

podを使ってAlamofire,SwiftyJSON,PromiseKitを入れました
podの使い方については良い記事がたくさんありますのでそちらを参照ください

PodFile
target 'promiseme' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for promiseme
  pod 'SwiftyJSON'
  pod 'PromiseKit'
  pod 'Alamofire'
  
end

3分クッキング方式です
メインのViewにUITextViewとUIButtonを配置して参照させておきます
簡単にですがqiitaの記事一覧取得とURLが有効かを確認する通信処理を関数化して用意しておきます

ViewController.swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBOutlet weak var txtArea: UITextView!
    @IBAction func btnDown(_ sender: Any) {
    }

    func getQiitaList( afterFunc: @escaping (_ url:String)->Void ){
        
        let url = URL(string: "http://qiita.com/api/v2/items")!
        let parameters: Parameters = ["page": 1, "per_page": 10]
        Alamofire.request(url, method: .get, parameters: parameters )
            
            .responseJSON { response in
                
                switch response.result {
                    
                // 処理成功時
                case .success(let value):
                    
                    let json = JSON(value)
                    let arrJson = json.array
                    for m in arrJson! {
                        let url = m["url"].rawString() ?? ""
                        afterFunc(url)
                        return
                    }
                    break
                // 処理失敗時
                case .failure(let error):
                    print(error)
                    break
                }
        }
    }
    
    func getQiitaTerm( strUrl:String, afterFunc: @escaping (_ text:String)->Void ){
        
        let url = URL(string: strUrl)!
        Alamofire.request(url, method: .get )
            .response { response in
                
                guard let status = response.response?.statusCode else { return }
                let text = strUrl + " " + String(status)
                afterFunc( text )
                
        }
    }

やりたいことは

記事取得ボタンを押すとQiitaの記事一覧を取得する
するとレスポンスにURLが含まれているので記事の1件についてそのURLをGETしてHTTPステータスをURLとともに表示します
内容は変でしょうもないですがご勘弁ください

screenshot.png

まず準備として通信処理関数には通信処理が完了したら実行しておきたいコールバック処理を渡すようにしてあります
なので呼び元ではコールバック処理を定義しておいて渡すことになります

コード悪い例

すごく単純に書いた場合はこのように書けるんじゃないでしょうか

ViewController.swift
    @IBAction func btnDown(_ sender: Any) {
        
        self.txtArea.text = ""
        let afterFunc = { (_ url:String) in

            let afterFunc2 = { (_ text:String) in
                self.txtArea.text += text + "\n"
            }
            self.getQiitaTerm(strUrl: url, afterFunc: afterFunc2)

        }
        self.getQiitaList(afterFunc: afterFunc)
        
    }

特に問題なく動きます
でもどうでしょうか?
コールバック処理の中にコールバック処理が書いてあって、すごく少ない処理なのにもう嫌になってきませんか?
これで処理の中身が複雑になったり長くなったり、コールバックの回数が増えていくとネストがすごすぎて可読性が悪化していきます
これを無名関数で書いていくともっと分かりにくいかもしれません

コード改善例

PromiseKitを使って改善します

ViewController.swift
    @IBAction func btnDown(_ sender: Any) {
        
        self.txtArea.text = ""
//        let afterFunc = { (_ url:String) in
//
//            let afterFunc2 = { (_ text:String) in
//                self.txtArea.text += text + "\n"
//            }
//            self.getQiitaTerm(strUrl: url, afterFunc: afterFunc2)
//
//        }
//        self.getQiitaList(afterFunc: afterFunc)
        
        
        _ = Promise<String> { seal in

            self.getQiitaList(afterFunc: { (_ url:String) in
                seal.fulfill(url)
            })

            }.then { url in Promise<String>
                { seal in
                    self.getQiitaTerm(strUrl: url, afterFunc: { (_ text:String) in
                        seal.fulfill(text)
                    })
                }
            }.then { text in Promise<Void>
                { seal in
                    self.txtArea.text += text + "\n"
                    seal.fulfill_()
                }
            }.then { Void in Promise<Void>
                { seal in
                    // 他のコールバック処理好きなだけ
                    seal.fulfill_()
                }
            }.then { Void in Promise<Void>
                { seal in
                    // 他のコールバック処理好きなだけ
                    seal.fulfill_()
                }
            }.ensure {
                // nothing to do

            }.catch { error in
                print(error)
        }
        
    }

余計に長くなってしまった・・・
あんまり参考にならんかも

けどこれでコールバックによってはネストがこれ以上は深くならずに済みます
この難しさを1度許容してしまえば後はこれ以上は難しくならずに済みます
処理の順番も意識しやすくなるんじゃないでしょうか
Promiseに限らず非同期処理を扱う仕組みや書き方というのはありますので**「適切」**に用いるのが良いでしょう

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?