🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

1回答

795閲覧

Swift クロージャーのキャプチャと参照型について

moriman

総合スコア615

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2019/12/24 17:49

https://docs.swift.org/swift-book/LanguageGuide/Closures.html
上記ページを読んでいていまいちスッキリしない状況なのですが、
まずCapturing Valuesの節で

Swift

1func makeIncrementer(forIncrement amount: Int) -> () -> Int { 2 var runningTotal = 0 3 func incrementer() -> Int { 4 runningTotal += amount 5 return runningTotal 6 } 7 return incrementer 8} 9 10let incrementByTen = makeIncrementer(forIncrement: 10) 11 12incrementByTen() 13// returns a value of 10 14incrementByTen() 15// returns a value of 20 16incrementByTen() 17// returns a value of 30 18

上記のようなよくあるカウンターのサンプルが示されています。
javascriptでもクロージャーのところでこのようなカウンターのサンプルはあり、動きもほぼ同じなので、コードの動きはだいたいわかるのですが、その下の節で
Closures Are Reference Types
とあり、クロージャーが参照型である解説が出てきます。
参照型の話でARC(自動参照カウント)ともつながってくると思うのですが、そこらへんを読んでいて、
クラスとクロージャーは参照型、
Int、Stringなどは構造体なので値型。
参照型はARCが適用されるが、値型は参照型ではないのでARCは適用されない。
みたいなことが
https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
に書いてあると思います。

そこでキャプチャ(カウンターのサンプル)なのですが、外部の定数incrementByTenにincrementer関数がセットされ、関数incrementerはクロージャーなので参照型、ということでmakeIncrementer関数の実行が終了してもARCの仕組みにより、incrementByTenがincrementer関数を参照している間はincrementer関数が消されず、

incrementByTen()

とすることでincrementer関数が何度でも実行でき、状態を保持しているような挙動が実現できる、ということはわかるのです。
ただ、makeIncrementer内の変数runningTotalと引数amountは、本来makeIncrementer関数の実行が終わると消えてしまうものだと思うのですが、しかしincrementer関数によりキャプチャされているので、makeIncrementer関数実行終了後も消されずに、まるでカウンターが状態を保持しているような挙動を実現している、ということだと思うのですが、これはARCの仕組みと非常に似ていると思います。ただ、runningTotalとamountはどちらもInt型なので値型です。ARCの仕組みが適用されるのは参照型、という説明と食い違うのですが、キャプチャはARCの仕組みとは関係ない機能なのでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

参照カウンタの方式とご質問の内容については関係ありません。

runningTotalは関数makeIncrementer(forIncrement:)が実行された後は破棄されるはずとの疑問ですが、関数makeIncrementer(forIncrement:)からの参照はなくなりますが、関数incrementer()が参照を持っているので破棄されません。

関数makeIncrementer(forIncrement:)からの参照がなくなっていることを確かめるために、例示のコードに続けて

swift

1let f = makeIncrementer(forIncrement: 3) 2print(f()) 3print(f()) 4 5print(incrementByTen())

などとしてください。
incrementByTenfrunningTotalが独立しているのが分かります。
これは関数makeIncrementer(forIncrement:)の実行のたびにrunningTotalが生成され、実行後その参照を切り離しているためです。

また

swift

1var runningTotal = 0 2 3func makeIncrementer(forIncrement amount: Int) -> () -> Int { 4 func incrementer() -> Int { 5 runningTotal += amount 6 return runningTotal 7 } 8 return incrementer 9} 10 11let incrementByTen = makeIncrementer(forIncrement: 10) 12let incrementByThree = makeIncrementer(forIncrement: 3) 13 14print(incrementByTen()) 15print(incrementByTen()) 16print(incrementByThree())

とした場合、関数incrementer()がキャプチャするrunningTotalがすべて同一のものであるため、例示のコードとは違う結果になることも確認してください。

投稿2019/12/25 01:41

MasakiHori

総合スコア3391

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

moriman

2019/12/26 10:22 編集

回答とサンプルコードを頂きましてありがとうございます。 ドキュメントの構成としてキャプチャの説明のすぐ下に「クロージャーは参照型」ということで参照型の話が出てくること、 さらにARCにreference、参照という言葉が使われ、一方でキャプチャの説明の中にも If you create a second incrementer, it will have its own stored reference to a new, separate runningTotal variable: (makeIncrementerを実行して)二つ目のincrementer関数を作ったら、新しい独立した変数runningTotalへの参照を持つでしょう。 のようにreferenceという言葉が使われているので私が両者を混同した、ということだと思うのですが。 実際言葉の使い方として間違いではないので両方で「reference,refer」を使う分にはいいと思うのですが、読み手としては、ARCの説明の中の「reference、参照」と、キャプチャの説明の中で使われる「reference、参照」は同じ単語ではあるが、プログラミングの話の中での意味としては別のものである、という認識が必要、ということですよね。 ARCの説明中のreferenceは、自動参照カウントや参照型の話の中でのメモリのアドレス的なもの。 一方でキャプチャの説明中のreferenceは、単に「変数にアクセスすること」くらいの意味で、区別して認識する必要がある、ということかと感じたのですが、その理解で問題ないでしょうか。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問