web-dev-qa-db-ja.com

Swift #selector構文で「あいまいな使用」コンパイルエラーを解決するにはどうすればよいですか?

[NOTEこの質問はもともとSwift 2.2で作成されました。 Swift 4で改訂され、2つの重要な言語の変更が含まれます。最初のメソッドパラメーターexternalは自動的に抑制されなくなり、セレクターをObjective-Cに明示的に公開する必要があります。

クラスに次の2つのメソッドがあるとします。

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

次に、Swift 2.2の新しい#selector構文を使用して、これらのfirstに対応するセレクターを作成します。メソッド、func test()。どうすればいいのですか?私がこれを試すとき:

let selector = #selector(test) // error

...「test()のあいまいな使用」というエラーが表示されます。しかし、私がこれを言うと:

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

...エラーはなくなりますが、今は間違ったメソッドwithパラメーター。パラメーターを指定せずにを参照します。どうすればいいのですか?

[注:この例は人為的ではありません。 NSObjectには、Objective-Cのcopycopy:の両方のインスタンスメソッド、Swift copy()copy(sender:AnyObject?);があります。そのため、問題は現実の生活で簡単に発生する可能性があります。]

70
matt

[NOTEこの回答は、もともとSwift 2.2で作成されました。 Swift 4で改訂され、2つの重要な言語の変更が含まれます。最初のメソッドパラメーターexternalは自動的に抑制されなくなり、セレクターをObjective-Cに明示的に公開する必要があります。

この問題を回避するには、正しいメソッドシグネチャへの関数参照をcastingします。

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

(ただし、私の意見では、これを行う必要はないはずです。この状況をバグと見なし、関数を参照するためのSwiftの構文が不適切であることを明らかにしました。バグレポートを提出しましたが、役に立ちません。)


新しい#selector構文を要約するだけです:

この構文の目的は、セレクターをリテラル文字列として提供するときに発生する可能性のあるすべての一般的なランタイムクラッシュ(通常は「認識されないセレクター」)を防ぐことです。 #selector()関数参照を受け取り、コンパイラーは関数が実際に存在することを確認し、Objective-Cセレクターへの参照を解決します君は。したがって、間違いをすぐに犯すことはできません。

EDIT:はい、できます。完全な塊になり、ターゲットを#selectorで指定されたアクションメッセージを実装しないインスタンスに設定できます。コンパイラーは 'あなたを止めると、あなたは古き良き時代のようにクラッシュします。ため息...)

関数参照は、次の3つの形式のいずれかで表示できます。

  • bare name関数の。関数が明確であれば、これで十分です。したがって、たとえば:

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

    testメソッドは1つしかないため、この#selectorはパラメーターを取り、#selectorがパラメーターに言及していない場合でもそれを参照します。舞台裏で解決されたObjective-Cセレクターは、"test:"のままです(コロンはパラメーターを示します)。

  • その署名の残りの部分と共に関数の名前。例えば:

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

    2つのtestメソッドがあるため、区別する必要があります。表記test(_:)は、secondの1つ、つまりパラメーターを持つものに解決されます。

  • シグネチャの残りを含むまたは含まない関数の名前、およびパラメータのtypesを表示するa cast副<文>この[前述の事実の]結果として、それ故に、従って、だから◆【同】consequently; therefore <文>このような方法で、このようにして、こんなふうに、上に述べたように◆【同】in this manner <文>そのような程度まで<文> AひいてはB◆【用法】A and thus B <文>例えば◆【同】for example; as an example:

    @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)
    }
    

    ここでは、overloadedtest(_:)があります。 Objective-Cはオーバーロードを許可しないため、オーバーロードをObjective-Cに公開することはできません。そのため、そのうちの1つのみが公開され、isセレクタはObjective-Cの機能であるため、公開されています。ただし、Swiftに関する限り、stillで曖昧性を排除する必要があり、キャストはそれを行います。

    (私の意見では誤用されていますが)上記の答えの基礎として使用されているのは、この言語機能です。

また、Swiftが関数のクラスを伝えることで関数参照の解決を支援する必要があるかもしれません。

  • クラスがこのクラスと同じ場合、またはこのクラスのスーパークラスチェーンの上位にある場合、通常、これ以上の解決は必要ありません(上記の例に示すように)。オプションで、ドット表記でselfと言うことができます(例:#selector(self.test)。場合によってはそうする必要があります)。

  • それ以外の場合は、この実際の例のように、ドット表記でメソッドが実装されるinstanceへの参照を使用します(self.mpはMPMusicPlayerControllerです):

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

    ...または、classの名前をドット表記で使用できます:

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

    (これは、testがインスタンスメソッドではなくクラスメソッドであるように見えるので、奇妙な表記のように見えますが、それでもセレクターに正しく解決されます。それがすべてです。)

98
matt