開発メモ

開発関係のメモをいろいろと。たぶん。

Swiftにおけるselfとクロージャーと@noescape

Swiftにおけるselfとクロージャーと@noescapeと簡単にまとめてみました。

※Swift 2.1時点での情報です

動作を確認した環境

環境 情報
Xcode 7.2.1 (7C1002)
iOS 9.2
Swift 2.1.1
Date 2016/3/10

用語説明

selfとは?

公式の情報は The Swift Programming Language (Swift 2.1): Methods の『The self Property』にあります。

Every instance of a type has an implicit property called self, which is exactly equivalent to the instance itself. You use the self property to refer to the current instance within its own instance methods.

簡単に訳すと、

selfと呼ばれるプロパティを全てのインスタンスが持ってます。それは、自分自身を現します。インスタンスメソッドの中で、現在のインスタンスを参照するためにselfプロパティを使う事が出来ます。

といった感じでしょうか。つまり、自分自身ですね。

クロージャーとは?

公式の情報は The Swift Programming Language (Swift 2.1): Closures にあります。

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.

これは

クロージャーは自分自身を含んだ関数のようなブロックで、他に渡してコードから使う事が出来ます。Swiftのクロージャーは、CやObjective-Cにおけるのブロックや他の言語で言うところのラムダ式に似てます。

といった感じでしょうか? 関数のようなブロックなんですね。それだけで無くクロージャーには、作られたときの周囲の定数や変数をキャプチャーする働きもあったりします。

この辺りの話は The Swift Programming Language (Swift 2.1): Closures を参照して下さい。

@noescapeとは?

公式の情報は The Swift Programming Language (Swift 2.1): Attributes の『Declaration Attributes』にあります。

Apply this attribute to a function or method declaration to indicate that a parameter will not be stored for later execution, such that it is guaranteed not to outlive the lifetime of the call. Function type parameters with the noescape declaration attribute do not require explicit use of self. for properties or methods. For an example of how to use the noescape attribute, see Nonescaping Closures.

面倒なのでいちいち訳しませんが、簡単に言うと、noescape属性をつけたら渡された引数(クロージャー)を保存したり後から呼び出したりしないよ、と。そういう事ですね。そして、具体的な使い方は The Swift Programming Language (Swift 2.1): Closures にありますよ、と。

selfの具体的な使用例

同じ名前のローカル変数でプロパティが隠されてるときに使用する

明示的にプロパティを使うためにselfをつけるケースですね。init()でよく使われる形です。

class JunkClass1
    var name = ""
    init(name: String) {

        // 引数のnameで隠蔽されているため、プロパティを参照する方にselfを使用
        self.name = name
    }
}

自分自身を他の変数に渡すときに使う

自分自身を変数のように扱うケースですね。例はUITableViewを使うときのお約束の形です。

class JunkClass2: NSObject, UITableViewDataSource, UITableViewDelegate {
    func setupTableView(tableView: UITableView) {

        // 自分自身をdelegateやdataSourceに設定するため、selfを使用
        tableView.delegate = self
        tableView.dataSource = self
    }
    
    以下略

クロージャーの中でプロパティを参照するために使う

クロージャーの中で自分自身のプロパティやメソッドを呼び出すために、明示的にselfをつけるケースですね。

用語説明ではさらっと流しましたが、クロージャーは周囲の環境をキャプチャーしてくれます。では、具体的に何をキャプチャーしてくれるのかというと、そのクロージャーの中で使用している定数や変数です。

ただ、暗黙のうちに使用しているselfまではキャプチャーしてくれないので、明示的に使用してキャプチャーしてもらうわけですね。たぶん。

class JunkClass3
    var name = ""

    /// クロージャーを使用する例
    func useClosure() {

        let no = 20

        // 接尾形式でクロージャーを渡す
        execClosure() {

            // 自動でキャプチャーされるのでそのまま使用可能
            print("no: \(no)")

            // この場合、この書き方ではエラーになる
            // → 周囲の環境にnameが見つからないため。かな?
//         print("name: \(name)")

            // クロージャーの中でプロパティを参照するためにselfを使用
            // → selfがキャプチャーされ、self.nameが使用可能になる
            print("name: \(self.name)")
        }
    }

    /// 渡されたクロージャーを呼び出すだけのメソッド
    func execClosure(closure: (()->Void)) {
        closure()
    }
}

selfをつける事でプロパティが使えるようになってめでたしめでたし・・・ と終わればよかったんですが、これはこれで、循環参照といった問題を生み出す事になってしまいます。

循環参照を簡単に説明すると、蛇が自分のしっぽを食べようとするようなお話です。

『selfが何かプロパティを持ってて、そのプロパティがクロージャーを持ってて、そのクロージャーがselfをキャプチャーしてる』

といった感じですね。循環参照の何が悪いかというとメモリリークが起きる事で、これを回避しようと思ったら・・・ この話は果てしなく長くなるので、ここでは省略します。

@noescapeの使用例

クロージャーの中でいちいちselfを付けるのが面倒』とか『循環参照対策が面倒』と言った意見に応えて作られたのが@noescape属性です。たぶん。・・・あれ? 循環参照対策にはなってない?

@noescape属性が付けられたクロージャーはそのメソッドの中でしか使われません。 これが何を意味してるかというと、メソッドを抜けた時点で、キャプチャーしていた定数や変数を解放する事が出来るようになるという事です。これによって循環参照は発生しなくなりました。

また、キャプチャーしても循環参照が発生しない事がはっきりしてるので、サービスとして(?)自動的にselfをキャプチャーしてくれるようになります。つまり、クロージャーの中でいちいちselfを付けなくてもよくなります。

class JunkClass4 {
    var name = ""

    /// @noescapeのクロージャーを使用する例
    func useClosureNoescape() {

        let no = 20

        // 接尾形式でクロージャーを渡す
        execClosureNoescape() {

            // ローカル変数の動作は同じ
            print("no: \(no)")

            // 自動でselfをキャプチャーしてくれるので、そのまま使用可能
            print("name: \(name)")

            // 明示的にselfを付けても動作は同じ
            print("name: \(self.name)")
        }
    }

    /// 渡されたクロージャーを呼び出すだけのメソッド
    /// → 引数に@noescape属性を付けただけ
    func execClosureNoescape(@noescape closure: (()->Void)) {

        // @noescapeが付いてるので、ここでしか使えない
        closure()
    }
}

@noescapeの使い方は、クロージャーを受け取る側で引数に@noescape属性を付けるだけです。簡単ですね。

出来れば積極的に使っていきたい@noescapeですが、残念ながら、使用できる場所は限られています。何かイベントが発生したときに呼び出されるようなクロージャーを、@noescapeにすることは出来ません。

そういうときは・・・ がんばって、普通のクロージャーを使いこなしましょう。