SwiftUI の dismiss() って「関数()」じゃなくて「オブジェクト()」だよね?

はじめに

SwiftUI で多用されている Property Wrappers ですが、基本的に「なんか難しいことをやってくれてる」感じになってます。頻繁に利用される @Environment も、「こう書けばこう動く」という感じで何となく使っている人が多いと思います。

ところで、Property Wrapper そのものの機能とは関係ないですが、以下のコードの @Environmentdismiss() の部分はよく考えると不思議な構文ではないでしょうか。

// ビューを閉じるための DismissAction を取得
@Environment(\.dismiss) private var dismiss: DismissAction

...

// ビューを閉じる
dismiss() // <--- オブジェクト() という構文???

dismissDismissAction 型のオブジェクトなので、本来なら以下の形式でないと辻褄が合いませんよね。

dismiss.何かメソッド名() // <--- オブジェクト.メソッド名() という構文ならわかる

検証環境

  • Xcode 15.0
  • Swift 5.9

callAsFunction メソッド

実は、これは dismiss.何かメソッド名() の省略形です。 具体的には dismiss.callAsFunction() です。

callAsFunction の例

例として、単に「足し算をするためだけの構造体」を用意し、callAsFunction メソッドを実装して利用してみます。

import Foundation

// 足し算をするためだけの構造体
struct AddAction {
    public func callAsFunction(_ number1: Int, with number2: Int) -> Int {
        return number1 + number2
    }
}

let add = AddAction()

// 以下は add.callAsFunction(10, with: 20) の省略形
let result1 = add(10, with: 20)
print(result1)

// もちろん、メソッド名を書いても動作する
let result2 = add.callAsFunction(50, with: 100)
print(result2)

上記のコードでは、addAddAction 型のオブジェクトですが、add(10, with: 20) の形で実行できていることがわかります。

冒頭で紹介した DismissAction にも callAsFunction メソッドが定義されているため、dismiss() と記述できます。

callAsFunction メソッドの仕様

  • クラス、構造体、列挙型に宣言する
  • メソッド名は callAsFunction
  • 引数の型と数、戻り値の型は自由
  • オブジェクト.callAsFunction(引数)の形式で利用

注意点

callAsFunction というメソッド名は何らかのプロトコルで規定されているわけではないため、定義時にスペルミスをしてもエラーにはならない点に注意が必要です。 呼び出し側のコードを書いている時に、オブジェクト(引数) で呼べないので気づくこととなるでしょう。

まとめ

callAsFunction を積極的に定義する機会は多くないと思います。 しかし、Swift(特に SwiftUI)では意外と使われていたりするので、仕組みは知っておくと良いでしょう。

以前に書いた記事 SwiftUI でサイズクラスに対応する でも、軽く触れています。

作成したサンプルは、GitHub: aokiplayer/swift-sandboxCallAsFunction.playground に置きました。

参考