Swift #selector 구문으로 "모호한 사용"컴파일 오류를 해결하려면 어떻게해야합니까?


79

[ 참고이 질문은 원래 Swift 2.2에서 공식화되었습니다. 두 가지 중요한 언어 변경을 포함하여 Swift 4에서 수정되었습니다. 첫 번째 메소드 매개 변수 외부는 더 이상 자동으로 억제되지 않으며 선택기는 Objective-C에 명시 적으로 노출되어야합니다.]

내 수업에 다음 두 가지 메서드가 있다고 가정 해 보겠습니다.

@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?); 실생활에서 쉽게 문제가 발생할 수 있습니다.]

답변:


110

[ 참고이 답변은 원래 Swift 2.2에서 공식화되었습니다. 두 가지 중요한 언어 변경을 포함하는 Swift 4 용으로 수정되었습니다. 첫 번째 메소드 매개 변수 외부는 더 이상 자동으로 억제되지 않으며 선택기는 Objective-C에 명시 적으로 노출되어야합니다.]

함수 참조를 올바른 메서드 서명 으로 캐스팅 하여이 문제를 해결할 수 있습니다 .

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

(하지만 제 생각에는 이렇게 할 필요가 없습니다. 저는이 상황을 버그로 간주하여 함수를 참조하는 Swift의 구문이 부적절하다는 것을 보여줍니다. 버그 보고서를 제출했지만 소용이 없습니다.)


#selector구문 을 요약하면 다음 과 같습니다.

이 구문의 목적은 선택자를 리터럴 문자열로 제공 할 때 발생할 수있는 너무 흔한 런타임 충돌 (일반적으로 "인식되지 않은 선택자")을 방지하는 것입니다. 함수 참조를#selector() 취하고 컴파일러는 함수가 실제로 존재하는지 확인하고 Objective-C 선택기에 대한 참조를 해결합니다. 따라서 쉽게 실수 할 수 없습니다.

( 편집 : 예, 할 수 있습니다. 완전한 lunkhead가 될 수 있으며에서 지정한 작업 메시지를 구현하지 않는 인스턴스로 대상을 설정할 수 있습니다 #selector. 컴파일러는 당신을 멈추지 않을 것이고 당신은 좋은 옛날. 한숨 ...)

함수 참조는 다음 세 가지 형식 중 하나로 나타날 수 있습니다.

  • 베어 이름 함수의. 함수가 모호하지 않으면 충분합니다. 따라서 예를 들면 다음과 같습니다.

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

    test메서드 는 하나뿐 이므로 #selector매개 변수를 사용하고 #selector매개 변수를 언급하지 않더라도 참조합니다. 배후에서 해결 된 Objective-C 선택기는 여전히 올바르게 "test:"(콜론을 사용하여 매개 변수를 나타냄) 그대로입니다.

  • 나머지 시그니처 와 함께 함수의 이름 . 예를 들면 :

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

    두 가지 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(_:) . 오브젝티브 C는 오버로드를 허용하지 않기 때문에 과부하 때문에 단지 그들 중 하나가 노출되어, 오브젝티브 C에 노출 될 수 없으며, 우리는 단지 하나에 대한 선택 형성 할 수 있다 셀렉터는 오브젝티브 C의 기능이기 때문에, 노출을 . 그러나 우리는 스위프트에 관한 한 여전히 명확하게 해야합니다 .

    (내 의견으로는 오용되는 것은 위의 답변의 기초로 사용되는 언어 적 기능입니다.)

또한 함수가 어떤 클래스에 있는지 알려줌으로써 Swift가 함수 참조를 해결하도록 도와야 할 수도 있습니다.

  • 클래스가이 클래스와 같거나이 클래스의 상위 클래스 체인보다 높은 경우 일반적으로 추가 해결이 필요하지 않습니다 (위의 예에 표시된대로). 선택적 self으로 점 표기법으로 말할 수 있습니다 (예 : #selector(self.test), 일부 상황에서는 그렇게해야 할 수도 있습니다.

  • 그렇지 않으면 이 실제 예제 ( MPMusicPlayerController 임) 에서와 같이 점 표기법과 함께 메서드가 구현 된 인스턴스에 대한 참조를 사용합니다 self.mp.

    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인스턴스 메소드가 아닌 클래스 메소드 라고 말한 것처럼 보이지만 그럼에도 불구하고 선택기로 올바르게 해석 될 것입니다. 그게 전부입니다.)


2
안녕하세요 @Sulthan, 여러분의 의견을 환영합니다. -아니, 그것은 함수 호출로 해석되었습니다. "매개 변수가없는 것"이라는 개념을 직접 표기 할 방법은 없습니다. 그것은 구멍입니다. 그들은 (그렇게 자주) 완전히 생각하지 않고 이것을 진행 한 것 같습니다 ...
matt

4
@Sulthan 내가 두려워했던 것처럼, 버그 보고서가 "의도 한대로 작동"했다. 내 대답은 그래서 대답 : 당신 사용하는 노 매개 변수의 변형을 지정하기 위해 표기법을. as
matt

1
"놀라운"경험의 또 다른 하이라이트는 Swift로 코딩하는 것입니다.
Leo Natan 2016

5
현재 Swift 3에서는 인수 목록을 괄호 안에 넣어야합니다 : let selector = #selector(test as (Void) -> Void).
Martin R

1
아마도 가장 좋은 곳은 아니지만 Swift 3에서 선호되는 구문은 무엇입니까? test as (Void) -> Void또는 더 짧은 구문 test as () -> ()?
Dam

1

누락 된 명확성을 추가하고 싶습니다. 클래스 외부에서 인스턴스 메서드에 액세스합니다.

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

의 전체 서명 클래스 '의 관점에서 test()방법은 (Foo) -> () -> Void당신이를 얻기 위해 지정해야합니다 Selector.

#selector(Foo.test as (Foo) -> () -> Void)
#selector(Foo.test(_:))

또는 Selector원래 답변에 표시된대로 인스턴스의를 참조 할 수 있습니다 .

let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))

예,이 표기법 Foo.xxx은 외부 클래스 메서드가 아니기 때문에 이미 이상합니다. 따라서 컴파일러가 패스를 제공하는 것처럼 보이지만 모호성이없는 경우에만 해당됩니다. 모호한 부분이 있으면 소매를 롤백하고 더 긴 표기법을 사용해야합니다. 이는 인스턴스 메서드가 "은밀하게"카레 클래스 메서드이기 때문에 합법적이고 정확합니다. 남은 가장자리 케이스를 아주 정밀하게 감지합니다!
matt

0

제 경우 (Xcode 11.3.1) 오류는 디버깅하는 동안 lldb를 사용할 때만 발생했습니다. 실행할 때 제대로 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.