1. ホーム
  2. swift

[解決済み] Swiftの#selector構文による「ambiguous use of」コンパイルエラーの解決方法とは?

2023-06-24 22:57:07

質問

[ 注意 この質問はもともとSwift 2.2のもとで作成されました。それはSwift 4のために修正され、2つの重要な言語の変更を含んでいます:最初のメソッドパラメータの外部はもはや自動的に抑制されず、セレクタは明示的にObjective-Cに公開しなければなりません]。

私のクラスにこれらの2つのメソッドがあるとします。

@objc func test() {}
@objc func test(_ sender:AnyObject?) {}

では、Swift 2.2の新しい #selector 構文を使って 最初の に対応するセレクタを作ることができます。 func test() . どうすればいいのでしょうか?これを試すと

let selector = #selector(test) // error

... というエラーが表示されます。 test() ." しかし、もし私がこう言うなら。

let selector = #selector(test(_:)) // ok, but...

...エラーは消えますが、今度は 間違ったメソッド を参照しています。 をパラメータとするもの。を参照したいのですが がなく を参照したいのですが、パラメータがありません。私はそれを行うにはどうすればよいですか?

[注意:この例は人工的なものではありません。NSObjectはObjective-Cの両方の copycopy: インスタンスメソッド、Swift copy()copy(sender:AnyObject?) というように、実生活でも簡単に問題が発生します] 。

どのように解決するのか?

[ 注意 この回答はもともとSwift 2.2のもとで作成されました。それはSwift 4のために修正され、2つの重要な言語の変更を含んでいます:最初のメソッドパラメータの外部は、もはや自動的に抑制されず、セレクタは明示的にObjective-Cに公開されなければなりません].

この問題を回避するために キャスティング を正しいメソッドシグネチャにキャストすることで回避できます。

let selector = #selector(test as () -> Void)

(しかし、私の考えでは、このようなことをする必要はないはずです。私はこの状況をバグとみなし、Swiftの関数参照の構文が不十分であることを明らかにしています。私はバグレポートを提出しましたが、無駄でした)。


ただ、要約すると、新しい #selector の構文があります。

この構文の目的は、セレクタをリテラル文字列として与えたときに発生する、あまりにも一般的なランタイムクラッシュ(通常は "認識されないセレクタ" )を防ぐことにあります。 #selector() 関数参照 を取ると、コンパイラはその関数が本当に存在するかどうかをチェックし、その参照をObjective-Cのセレクタに解決してくれます。したがって、容易に間違いを犯すことはできません。

( EDITです。 なるほど、できるんですね。完全なヘタレで、ターゲットを #selector . コンパイラはあなたを止めないので、昔と同じようにクラッシュします。はぁ...)

関数参照は3つの形式のいずれかで現れることができます。

  • 関数参照は ベアネーム を指定します。関数が曖昧でなければ、これで十分です。したがって、例えば

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test)
    }
    
    

    は1つだけです。 test メソッドは一つしかないので、この #selector はパラメータを取るにもかかわらず、それを参照し #selector がパラメータに言及していないにもかかわらず、を参照しています。解決された Objective-C セレクタは、舞台裏では、まだ正しく "test:" (となります(パラメータを示すコロン付き)。

  • の残りの部分と一緒に関数の名前を指定します。 シグネチャ . 例えば

    func test() {}
    func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test(_:))
    }
    
    

    私たちは、2つの test メソッドがあるので、区別する必要があります。 test(_:) 第二 に解決されます。

  • 関数の名前にシグネチャの残りを加えたもの、または省いたもの。 キャスト を表示します。 タイプ を表示します。このように

    @objc func test(_ integer:Int) {}
    @nonobjc func test(_ string:String) {}
    func makeSelector() {
        let selector1 = #selector(test as (Int) -> Void)
        // or:
        let selector2 = #selector(test(_:) as (Int) -> Void)
    }
    
    

    ここでは オーバーロード test(_:) . Objective-Cはオーバーロードを許可していないので、どちらか一方しか公開されず、セレクタを形成できるのは が公開される。セレクタはObjective-Cの機能であるからだ。しかし、私たちは まだ は、Swiftに関する限り曖昧さをなくし、キャストはそれを行います。

    (上記の回答の根拠として使われる-私の意見では誤用-のは、この言語的特徴です)。

また、関数がどのクラスにあるのかを伝えることで、Swiftが関数参照を解決するのを助けなければならないかもしれません。

  • クラスがこのクラスと同じか、このクラスからスーパークラス連鎖の上にある場合、さらなる解決は通常必要ありません(上記の例で示されるように); オプションとして、次のように言うことができます self というように、ドット・ノートを使って(例えば #selector(self.test) というように、状況によってはそうしなければならないかもしれません。

  • そうでない場合は インスタンス への参照を、ドット表記で使用します。 self.mp は MPMusicPlayerController です)。

    let pause = UIBarButtonItem(barButtonSystemItem: .pause, 
        target: self.mp, action: #selector(self.mp.pause))
    
    

    ...または、ファイル名として クラス のように、ドットノテーションで指定することもできます。

    class ClassA : NSObject {
        @objc func test() {}
    }
    class ClassB {
        func makeSelector() {
            let selector = #selector(ClassA.test)
        }
    }
    
    

    (と言っているように見えるので、不思議な記法に思えます)。 test がインスタンスメソッドではなくクラスメソッドであると言っているように見えるからです。しかし、それでもセレクタに正しく解決されるでしょう。)