SwiftでClosureを即時実行するtips

Monday, February 15, 2016

暖かかった日が過ぎてまた寒くなったせいで体がなんだかだるいですね。。
今日はよく自分が使うちょっとした小ネタを一つ。


Swiftのclosureは、主に関数の引数に取られることが多いのですが、変数として、

let double: Int -> Int = { return $0 * 2 }

こんな感じで定義ができます。(やや省略した書き方ですが。)
そして、これを実行するときは、()と、必要があればその中に引数を書いて渡してあげます。

print(double(2)) // -> 4

つまり、()を付けてあげることで、closureを実行することができるという形です。
ここで、次のように書き方を変えてみます。

let number = {
    return $0 * 2
}(2)
print(number)

さっきはdoubleという変数に(Int -> Int)の型のclosureを入れましたが、今回はclosureを実行した 結果 をnumberという変数に入れています。
これを、(自分の中では) closureの即時実行 と呼んでいます。
先の例だと恩恵をあまり感じられないのですが、

  • 何かオブジェクトを生成して、セットアップをして、変数に入れる場合
  • それだけのために関数を定義したくない場合
  • 変数のスコープを閉じ込めたい場合

なんかに使えます。
また、この即時実行はclosureを 保持しない ため、循環参照を防ぐことができます。
よく関数の型につける、@noescapeと同様の作用があります。
いくつか、例を出してみます。

  • UIViewを生成して、色々パラメータをいじる場合
// before
func setup() {
    let someView = UIView(frame: CGRectMake(0, 0, 100, 100))
    someView.backgroundColor = UIColor.redColor()
    someView.alpha = 0.5
    view.addSubview(someView)
}
// after
func setup() {
    let someView: UIView = {
        let v = UIView(frame: CGRectMake(0, 0, 100, 100))
        v.backgroundColor = UIColor.redColor()
        v.alpha = 0.5
        return v
    }()
    view.addSubview(someView)
}

毎回someView.~~~とやるのが、v.~~~で済むのでtype数も減らせますし、生成処理をclosureに閉じ込めることができます。

ちなみに話から逸れますが、こちらを更に使いやすくしたμライブラリとして、Thenがあります。 こっちのほうがよりスマートに書けますね!

// use then (thenのREADMEから抜粋)
let label = UILabel().then {
    $0.textAlignment = .Center
    $0.textColor = .blackColor()
    $0.text = "Hello, World!"
}

// use closure (thenのREADMEから抜粋)
let label: UILabel = {
    let label = UILabel()
    label.textAlignment = .Center
    label.textColor = .blackColor()
    label.text = "Hello, World!"
    return label
}()
  • lazy varの生成部分をclosureの即時実行で閉じ込める

自分はこれをよく使うかもしれません。
lazy varで宣言する変数が例えば、

lazy var hoge: Hoge = setupHoge()

func setupHoe() -> Hoge {
    let hoge = Hoge()
    hoge.name = "Hoge"
    return hoge
}

といった形でわざわざそれのために関数を作らないといけない場合があるので、これをclosureの即時実行を利用して、

lazy var hoge: Hoge = {
    let hoge = Hoge()
    hoge.name = "Hoge"
    return hoge
}()

といった形で閉じ込めます。一見closureを保持しているように見えますが、これも必要とされるタイミングに即時実行されるだけで、保持はしていないので、
循環参照等は気にせず使うことができます。
また、closure内で計算用に定義した変数などはclosure内のみ参照可能になるので、スコープの範囲が汚れる心配もありません。
一つ注意なのは、即時実行の返す値や引数が必ずどこかで推論できる形でないとエラーが起きます。
例を挙げると、

// ×
let someView = {
    let v = UIView(frame: CGRectMake(0, 0, 100, 100))
    v.backgroundColor = UIColor.redColor()
    v.alpha = 0.5
    return v
}()

// これは◯
let someView: UIView = {
    let v = UIView(frame: CGRectMake(0, 0, 100, 100))
    v.backgroundColor = UIColor.redColor()
    v.alpha = 0.5
    return v
}()

//これも◯
let someView = { () -> UIView
    let v = UIView(frame: CGRectMake(0, 0, 100, 100))
    v.backgroundColor = UIColor.redColor()
    v.alpha = 0.5
    return v
}()

といった形で、いずれかの形で、UIViewが帰ってくることが推論できないとエラーになります。

それぞれ書き方なり流派なりあると思いますが、局所的に用いることで、わざわざ関数を用意したり、計算用の変数が不要な範囲で参照可能になったりを防げるので、 是非使ってみてください。

techSwifttips

NSLocale.preferredLanguages()を使わずに、端末の使用言語が日本語かどうかを判定する

EnhancedAutoreleasePoolを作成しました。