質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
スコープ

スコープとは、プログラム内で変数名など、参照可能な有効範囲のことを指します。

エスケープ処理

エスケープ処理とは、一連の文字や一文字に対して、一定の規則に従って別の意味を適用する処理過程です。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

Swift

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

Q&A

解決済

2回答

1213閲覧

【Swift】関数にて引数にクロージャー式を持つ場合の構文及びクロージャー式のエスケープ属性のスコープ範囲の理解

beginner_Jiro

総合スコア10

スコープ

スコープとは、プログラム内で変数名など、参照可能な有効範囲のことを指します。

エスケープ処理

エスケープ処理とは、一連の文字や一文字に対して、一定の規則に従って別の意味を適用する処理過程です。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

Swift

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

1グッド

1クリップ

投稿2020/10/19 11:12

いつもお世話になっております。
Swiftの基本的な部分について現在学習中なのですが以下の点についてどなたかご教授願えませんでしょうか。

疑問点

①関数にて引数にクロージャー式を持つ場合の構文について
関数が引数にクロージャー式を受け取る場合に、ブロック内で引数として受け取った式を実行するコードにて不明点がございます。
関数を実行する際に、関数名(){引数として渡すクロージャーの式}と記述するのがいまいちイメージがつかないです。関数({引数として渡すクロージャーの式})とするならばイメージがつくのですが、前者の場合引数はなしと関数を呼び出しているようにしか思えません、、、。そもそも関数()のこの()の部分に引数として渡したい値を記載するという認識自体が間違えているのでしょうか。具体的なコードを下記に記載いたします。

②クロージャー式のエスケープ属性について
まず、エスケープ属性の自身の認識として、関数の引数がクロージャーの場合に、そのクロージャー式が関数のスコープ外で使用されることが想定される場合に付与する属性である。という認識です。
しかしながら、ブロック内で引数内に関数が代入されていることを確認するためにPrint文を記載すると、escape属性を付与する旨のコンパイルエラーが発生いたします。同様に具体的なコードを下記に記載いたします。

コード

①の疑問点の具体的なコード

Swift

1func executetwice(operation: () -> Void) { 2 operation() //プリントします 3} 4//関数呼び出し 5executetwice() { 6 print("プリントします") 7} 8//なぜ関数呼び出し箇所は下記のような書き方ではないのか。。。?これはコンパイルエラー 9//Missing argument label 'operation:' in call 10executetwice( { 11 print("プリントします") 12})

②の疑問点の具体的なコード

Swift

1func executetwice(operation:@escaping () -> Void) { 2//なぜ引数であるoperationの中身がクロージャーであることを確認するために下記のプリント文でエスケープ属性が必要なのか?(スコープ内なのに...) 3 print(operation) 4 operation() //プリントします 5}//もしかしてprintって新規スコープとして取り扱われる? 6 7//関数呼び出し 8executetwice() { 9 print("プリントします") 10} 11

以上となります。それぞれ自身の認識を記載しているのは、間違った認識の場合ご指摘願えたら幸いと思い記載いたしました。
冗長な文章となり申し訳ございませんが何卒ご説明・ご教授いただける方がおりましたらご回答いただきますようよろしくお願いいたします。

TsukubaDepot👍を押しています

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

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

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

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

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

guest

回答2

0

②の疑問点の具体的なコード

Swift

1func executetwice(operation: () -> Void) { 2 print(operation) // Converting non-escaping value to 'Any' may allow it to escape 3 operation() 4}

これだと、Converting non-escaping value to 'Any' may allow it to escape というエラーが出ます。

引数ラベルの後に @escaping というキーワードをいれれば、このエラーを回避することができますが、それだと一時的な回避に過ぎず、質問者さんへの本質的な回答となっていないように思えました。

クロージャは離脱していないはずなのに、なぜ @escaping を付けなければならないのか(実際にprint()の中でどのような処理が行われているのか知る方法はないので、「離脱しない」と明言できませんが)。

それは、print() に渡される際、Any としてキャストされるこによってクロージャとしての型情報が失われるため のようです。

コンパイラがクロージャと判断することができれば適切にコンパイルしてくれると思うのですが、型情報を失ってしまうと、そのクロージャがその後どのように使われるのかをコンパイラは追うことができません。なので、@escaping をつけるように指示してくるようです。

ちなみに、クロージャは離脱しないものの、一時的に離脱するものとして扱いたいのであれば、下記のような書き方で回避することもできるようです。

Swift

1func executetwice(operation:() -> Void) { 2 withoutActuallyEscaping(operation) { closure in 3 print(closure) 4 } 5 operation() 6}

- withoutActuallyEscaping(_:do:)
Allows a nonescaping closure to temporarily be used as if it were allowed to escape.

こちらの書き方だと、離脱しないクロージャを、離脱するクロージャとして一時的に扱うことを明示できるため、コンパイラがエラーを出すこともありません。

ちなみに、この辺りの議論は StackOverFlow の以下のスレッドで行っています。

投稿2020/10/21 02:02

編集2020/10/21 02:53
TsukubaDepot

総合スコア5086

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

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

beginner_Jiro

2020/10/21 02:43

TsukubaDepot様 お世話になっております。 ご回答いただきありがとうございます。 Printを行うことでクロージャーとしての型が損失されるわけですね。。。 print(closure)では引数としてのclosureを明示的に実行していないから@escapeが必要と漠然と捉えていましたがそのような理由があったとは。。。 一時的な回避。。。との記載がありますが、私が記載したコードは偶然にも外部で使用されるような書き方をしなかっただけでもしも、Printをした後、型損失が行われたoperationの中身を外部の変数で使用したりする(どうやってそんなことをするのかわかりませんが笑)とたとえescape属性をつけていてもコンパイルエラーになったりするのでしょうかね。。。
TsukubaDepot

2020/10/21 02:52

printを行う、というよりも、print の引数が Any となっており、そこで暗黙的に型変換が行われることが原因かと思います(print についてのリファレンスを見ていただければ引数の型についてはわかりますので、そちらをご参照いただければと思います)。print しなくとも、たとえばメソッド内の別の変数に as Any という形でキャストした上代入しても型情報が失われるので、@escaping 指定が必須となるようです。 ちなみに、外部の変数で使用する、というのがメソッド外の変数のことであれば、これは明らかに離脱するので @escaping が必要になると思いますが、今度は循環参照の問題も出てくると思います(いまXcodeはアップデート中で確認できませんが、間違っていたら修正します)。
beginner_Jiro

2020/10/21 04:35

TsukubaDepot様 お世話になっております。 上記の件、勘違いしておりました。「Printする際にAny型で渡される」という理解ですね。なるほど。 今自分でも実際に、PrintではなくAny型にキャストしてみるとTsukubaDepot様のご指摘通り escape属性の付与を求められました。 「一時的な回避・・・」以降の文言は的外れな文脈となってしまったことをお詫び申し上げます。 丁寧に回答していただきありがとうございました。 また機会がございましたら、ご回答いただければと思います。
TsukubaDepot

2020/10/21 04:51

「的外れな文脈」と断る必要はないと思いますよ。そこまで謙遜する必要はないと思います。 私の回答も正確性にはかけていると思いますし。 むしろ勉強できる良い機会になったと思っています。
lehshell

2020/10/21 05:47

TsukubaDepot さん、XCode 上で確認すると確かに Converting non-escaping value to 'Any' may allow it to escape とエラーが出ますね。 大変失礼しました。
guest

0

ベストアンサー

①関数にて引数にクロージャー式を持つ場合の構文について

Swift

1func executetwice(operation: () -> Void) { 2 operation() //プリントします 3} 4 5executetwice(operation: { 6 print("プリントします") 7})

と関数の引数にクロージャーを直接書けます。
また、元の呼び出しの書き方をしたいのであれば次のようにします。

Swift

1// func executetwice(operation: () -> Void) { 2func executetwice(_ operation: () -> Void) { 3 operation() //プリントします 4} 5 6executetwice( { 7 print("プリントします") 8})

②クロージャー式のエスケープ属性について

Swift

1func executetwice(operation:@escaping () -> Void) { 2//なぜ引数であるoperationの中身がクロージャーであることを確認するために下記のプリント文でエスケープ属性が必要なのか?(スコープ内なのに...) 3 print(operation) 4 operation() //プリントします 5}//もしかしてprintって新規スコープとして取り扱われる?

print(operation) では operation を実行していません。
そのため関数のスコープから逃れて実行するまで operation を保持する必要があります。
したがって @escaping を要求されていますね。
beginner_Jiro さんのご指摘通り、print(operation) については XCode 上で確認すると
Converting non-escaping value to 'Any' may allow it to escape
というエラーが出ますから直接の原因は Any にキャストされていることですね。

10月20日 追記
Swift の関数定義は、関数名(引数ラベル 仮引数: 型) { 文 } の構造です。
引数ラベルがない場合は、仮引数が引数ラベルを兼ねる。
引数ラベルが _ の場合は関数呼び出し側では引数ラベルを省略できます。
関数呼び出しは、関数名(引数ラベル: 実引数) の構造です。
クロージャー式は {(仮引数: 型) -> 型 in 文} の構造です。

Swift

1executetwice() { 2 print("プリントします") 3}

は後置クロージャ―を使用し、省略したクロージャーの書き方になります。

今回の関数定義と関数呼び出しを省略せずに書くと

Swift

1func executetwice(ope operation: () -> Void) { 2 operation() //プリントします 3} 4executetwice(ope: { () -> Void in 5 print("プリントします") 6})

となります。
また、クロージャー式をいったん変数に入れて使用すると

Swift

1var prn = {() -> Void in print("プリントします")} 2executetwice(ope: prn)

と書くことになります。

投稿2020/10/19 14:05

編集2020/10/21 06:09
lehshell

総合スコア1147

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

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

beginner_Jiro

2020/10/21 01:40

lehshell様 お世話になっております。 ご回答いただきありがとうございました。 両者ともに実際に改めてコードを書いて理解いたしました。 ①については、operation()の方とPrint(operation)の違いをよく理解していませんでした。 確かに実行しているoperation()はスコープから離れようがすでに実行済みですからキャプチャの必要がないですものね。。。 また、②についてですが様々な書き方をご教授いただきありがとうございます。 後置クロージャーをした省略した書き方なのですね。これは初めて知りました。 参考にしている書籍では急に説明もなしに異なった書き方をしてくるので困っておりました。 個人的、一旦省略しない書き方で書いて行き自分にあった(イメージしやすい)記載方法を確立して行こうと思います。 お忙しい中、迅速な回答いただきありがとうございました。 機会がございましたら、恐れ入りますが今後もご回答いただければと思います。 以上になります。
lehshell

2020/10/21 05:53

beginner_Jiro さん、print(operation) については XCode 上で確認すると確かに Converting non-escaping value to 'Any' may allow it to escape というエラーが出ますから直接の原因は Any にキャストされていることでした。 大変失礼しました。
beginner_Jiro

2020/10/21 07:03

lehshell様 お世話になっております。 当件について修正及び、ウェブドキュメントのご提示ありがとうございます。 こちら目を通させていただきました。 また何かございましたらよろしくお願いいたします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問