クロージャ(慣れないやつ)

クロージャを“変数”として持つことで、オブジェクトが「後から実行される処理」を自由に差し込める

これは、

  • AAA が「String を受け取って Void を返す関数」を持っている
  • その関数は 後から自由にセットできる
  • AAA が “処理を外部に任せる” ための仕組み

という意味です。

🔶 これは何のために使うの?

✨ ① コールバック(イベント)を受け取る時

たとえば「ボタンが押されたらこれを実行してね」のような用途。

使用側で処理を inject(注入)できる:

= SwiftUI の .onTapGesture { } と同じ発想。

✨ ② ロジックを構造体に渡したい時

処理内容を構造体の外で決めたいときに便利。

使う側:

Validator の中身は変えずに、判定ロジックだけ差し替えられる。
=「Strategy パターン」の代わりに使える。

✨ ③ 非同期処理や状態変化を外へ通知するとき

例えばダウンロード完了通知:

✨ ④ SwiftUI の ViewModel と View の連携

🔶 仕組み:関数も“値として扱える” から可能

Swift では 関数は first-class citizen(第一級オブジェクト)。
だから変数として保持できる。

(Int) -> String のような “型” を持っている

その型をプロパティとして宣言するだけ。

🔶 注意点①:強参照循環に注意

クロージャは捕捉(capture)するので retain cycle の元に。

例:

対策:

🔶 注意点②:デフォルト値がない場合は初期化必須

対策:

🔶 まとめ

できること内容
コールバックイベント発生時に実行される処理を注入
ロジック差し替えValidator や Strategy パターン的に利用
非同期通知完了時に何をするか外から指定
UI 連携ViewModel → View の通知

→ 「処理の内容を後から自由に変えたい」時に使う
→ SwiftUI でも大量に使われている基本テクニック

デフォルト値を入れたくない場合

初期値がない時はエラーになる

対策:init を書く

使う側:

例1

🔵 まず全体の流れ(超ざっくり)

  1. BBB のインスタンス(f)を作るときに
    d にクロージャ(関数)を渡している
  2. f.c(e:) が呼ばれると
    内部で d(e) が実行される
  3. つまり
    d に渡したクロージャが実行される

🔥 図解:全部の流れをつなげるとこうなる!

① BBB のインスタンス生成

これを理解するとすべてが繋がる。

なので、

② 関数呼び出し:f.c(e: “ab.com”)

③ c(e:) の中身

ここが今回の疑問ポイントだね。

流れはこう:

省略せず書くと

さらに「普通の関数」に置き換えて考えると
もし d が「普通のメソッド」だったらこう:

違いはたった一つ:

d の中身を、外から差し替えられるかどうか

クロージャを代入している瞬間を完全に書くと

🔥 超重要な一文

var d: (String) -> Bool は
「関数を持てるようにしただけ」
まだ何も実行していない

実行されるのは d(e) の瞬間だけ

例2

in の前について

Swiftのクロージャで

inの前には、クロージャの引数(パラメータ)を定義します。これは|引数名: 型|の形式で書き、引数がない場合は||、型推論される場合は型を省略できますが、inキーワードは引数リストとクロージャの本体(処理内容)を区切るための重要な記号です。 

基本的な構文:

具体例:

1. 引数と型指定がある場合:

name: String の部分がinの前に来ます。

2. 引数名のみで型推論される場合:

number がinの前に来ています。

3. $0$1などの短縮記法(引数名なし):

この場合、引数リスト自体が省略され、{の直後から処理内容が始まりますが、これは「引数がない」という意味ではなく、引数リストの省略形です。 

まとめ:
inの前の部分は、そのクロージャが受け取るデータの「名前(ラベル)」と「型」を指定する場所であり、これがクロージャの使いやすさや可読性を高める重要な要素となります。 

例3

実行の流れ

  1. startRectangleSelection()が呼ばれる
  2. クロージャの性質について
  • このクロージャ { text in ... } はすぐには実行されません
  • RectangleSelectionWindow内部で保持され、後で呼び出されるのを待ちます
  • 非同期とは少し違い、「コールバック関数」として渡されているイメージです
  1. selectionWindow?.orderFront(nil)が実行
  2. ユーザーが矩形選択を完了すると

重要なポイント

  • クロージャは非同期処理ではなく「コールバック」: 渡した時点では実行されず、RectangleSelectionWindow側でcompletion?(text)が呼ばれた時に初めて実行されます
  • DispatchQueue.main.asyncは非同期: クロージャ内のこの部分は確かに非同期で、UI更新をメインスレッドで安全に行うためのものです
  • 「{}から出ずに終了」について: クロージャは一度実行されたら最後まで実行されます。DispatchQueue.main.asyncの中身だけが非同期キューに入り、クロージャ自体は同期的に最後まで実行されます

大まかな認識は合っていますが、「非同期のように動く」というよりは「後で呼び出されるコールバック関数」という理解の方が正確ですね。

引数を追加する方法

RectangleSelectionWindowのイニシャライザに引数を追加する場合は、以下のように書きます:

呼び出し側

ポイント

  • completion引数は最後に配置すると、クロージャを{ }で書けて読みやすくなります(trailing closure構文)
  • 複数引数を追加する場合も同様に、completionの前に並べます

例:

クロージャの結果を分岐させる例

方法1: 列挙型(enum)を使う

方法2: 2つの別々のクロージャを使う

方法3: オプショナルで両方持つ(シンプル)

方法1: 列挙型(enum)を使う で質問があります。 Enumの性質についてです。 — if let text = performOCR(rect: currentRect) { completion?(.text(text)) } — ここで文字列が入っているのに — selectionWindow = RectangleSelectionWindow(captureImage: false) { result in switch result { case .text(let text): — case .text(let text) ここで変数の再初期化みたいになっているのはなぜですか?

いい質問です!これは列挙型の**関連値(Associated Values)**という仕組みです。説明しますね。

何が起きているか

1. 値を「包む」(Wrap)

2. 値を「取り出す」(Unwrap)

図解

なぜこの仕組みが必要?

列挙型は複数の異なる型を扱えます:

resultSelectionResult型なので、中身がStringなのかCGImageなのか開けてみないと分からないんです。

具体例で理解

変数名が同じだけど別物

たまたま同じtextという名前を使っているだけで、実際は別のスコープの別の変数なんです。

もっと分かりやすい例

列挙型は「型安全な箱」のようなものです。中に何が入っているか保証されるので、間違った型で取り出すことができません!

コメント

タイトルとURLをコピーしました