はじめに
ViewControllerにおいて、viewWillAppear
またはviewDidLayoutSubviews
などのタイミングで一度だけ実行したい処理をたまに書きたいことがあると思います。(viewDidLoad
に記述できるのであればもちろんその方がスマートですが)
dispatch_once は使えない
GCDのdispatch_once
はマルチスレッドを考慮したシングルトン生成のパターンによく利用されますが、アドレスを指定した処理であるため静的に配置される変数(static変数)に対してしか正しく処理できません。(アプリケーション実行中に本当に一度だけ実行したいケースにしか利用できない)
言い換えると、今回のようなViewControllerが生成された後で一度だけ処理したい、というようなケースには対応できません。
シンプルに書くと
以下の様な処理になるでしょうか?
var isFirst = true // 最初の処理かどうか
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// 最初の処理だったら実行して、フラグ用の変数はfalseに落とす
if isFirst {
isFirst = false
// 一度だけ実行したい処理
}
}
シンプルではありますが、以下のような欠点があるように感じます。
- 状態管理のための変数が追加されている。
- 判定処理やフラグ更新処理のために、本質的な処理が埋もれがちになる。
アプリケーションの規模が小さければ大したことありませんが、大きくなってくると見過ごせない問題になってきます。
クロージャで書いてみる
いくぶん実験的ではありますが、以下のように書いてみました。
typealias ExecuteOnce = () -> ()
// 一度だけ引数にしていされたクロージャを実行するクロージャを返す関数
func executeOnce(execute: () -> ()) -> ExecuteOnce {
var first = true
return {
if (first) {
first = false
execute()
}
}
}
class ViewController: UIViewController {
// 一度だけ実行したい処理
var someInitialize: ExecuteOnce = {}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// 処理を定義(selfキャプチャが不要であれば定数プロパティとして宣言可能)
someInitialize = executeOnce { [unowned self] in
// 一度だけ実行したい処理
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
someInitialize() // 何度呼びだされても実行されるのは一度だけ
}
}
多少コード量は増えましたが、フラグ変数を追加することなく一度だけ処理することを実現できました。また、全体的に宣言的でスッキリとしたコードに出来たような気がします。
最後に
最初は、dipatch_once
のようなイメージでBool変数のアドレスを渡すような以下のコードを思いつきました。
executeOnce(&isFirst) {
// 一度だけ実行したい処理
}
フラグ変数を自分で更新するよりはいくぶんスッキリしますが、フラグ変数を宣言する必要があるのは変わりません。
ということで、クロージャを活用した処理を考えてみたわけですが・・・他にスマートな方法をご存じの方がいましたら教えて下さい。