Swift
swift4

Swiftのクロージャを乱用したかった

 これは何か

swiftのクロージャを連続して使うとネストが深くなってしまうのでそれを回避した

どんな処理を書きたかったか

こんな感じで処理を書きたかった

func hogeProcess(completionHandler: @escaping (Bool) -> Void) {
    print("hoge")
    completionHandler(true)
}

func someProcess(completionHandler: @escaping (Bool) -> Void) {

    hogeProcess({ (success) in
        if success == false { completionHandler(false) }
        hogeProcess({ (success) in
            if success == false { completionHandler(false) }
            hogeProcess({ (success) in
                if success == false { completionHandler(false) }
...

                        completionHandler(success)

...
            })
        })
    })

}

someProcess() { (success) in
    // Check status
    print(success) // true
}

しかしネストが深すぎるのと、やろうとしていたことが hogeProcess を行う回数がが動的に変化するためどうしても汚くなってしまった

 解決方法(追記)

ループ毎に別の変数を用意してあげれば循環することなく実行される

func hogeProcess(completionHandler: @escaping (Bool) -> Void) {
    print("hoge")
    completionHandler(true)
}

func someProcess(completionHandler: @escaping (Bool)->Void) {
    var current: (Bool)->Void = {
        completionHandler($0)
    }
    for _ in 0..<3 {
        let inner = current
        current = { success in
            if success == false { completionHandler(false) }
            hogeProcess { success in
                inner(success)
            }
        }
    }
    current(true)
}

someProcess() { (success) in
    // Check status
    print(success) // true
}

これを実行すると

$ swift main.swift
hoge
hoge
hoge
true

となる

解決方法(自分で考えたやつ)

関数を変数に代入できることをすっかり忘れていたので、それを使う

func hogeProcess(completionHandler: @escaping (Bool) -> Void) {
    print("hoge")
    completionHandler(true)
}

func someProcess(completionHandler: @escaping (Bool) -> Void) {

    func hogeCompletionHandler(success: Bool) {
        completionHandler(success)
    }
    var functions = [hogeCompletionHandler]

    for var i in 0..<3 { // 例えば3回やる時
        func tmpProcess(success: Bool) {
            if success == false { completionHandler(false) }
            hogeProcess() { (success) in
                _ = functions.removeLast()
                functions.last!(success)
            }
        }
        functions.append(tmpProcess)
    }

    functions.last!(true)
}

someProcess() { (success) in
    // Check status
    print(success)
}

これを実行してみると、

$ swift main.swift
hoge
hoge
hoge
true

となる

何をしているか

関数を配列に入れてそれをよんでいる
展開するとこんな感じ

// 配列内の一番最後の関数
if success == false { completionHandler(false) }
hogeProcess() { (success) in
    _ = functions.removeLast() // 最後の関数を消す (消さないと循環してしまう)
    functions.last!(success)   // 更新された最後の関数を実行
}

これを再帰的に行うので一番最後のクロージャで最初に入れた関数が実行される仕組み

最初に試してダメだったやつ

最初に行けるのでは?と思ったけどダメだったやつ

func lastFunction() {
    print("last")
}
var function = lastFunction

for var i in 0..<3 {
    func process() {
        print("process")
        function()
    }
    function = process
}
function()

こうすると永遠に process という文字列を出力し続けてしまう

    function = process

の時点で function の中で function  を読んでしまう処理になってしまい、

{ // function の中身
    print("process")
    function()
}

こんな感じになってしまうのでダメだった

呼び出す関数名がユニークならばよかったが同一なので別物としてどこかに残しておく必要があったため配列に入れた

まとめ

こんな処理書きたくなかった…
これ以外にいい方法があれば教えてください