2016-02-26 59 views
57

[備註此問題最初是根據Swift 2.2制定的。它已被修訂爲Swift 4,涉及兩個重要的語言變化:第一個外部方法參數不再被自動抑制,並且選擇器必須明確暴露於Objective-C。]如何解決Swift #selector語法中「模糊使用」編譯錯誤?

比方說,我有這兩種方法我的課:

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

現在我想用雨燕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?);所以這個問題在現實生活中很容易出現。]

回答

83

[備註這個問題最初是在Swift 2.2下編寫的。它已被修訂爲Swift 4,涉及兩種重要的語言更改:第一個外部方法參數不再被自動抑制,並且選擇器必須明確暴露於Objective-C。]

您可以通過鑄造你的函數引用正確的方法簽名:

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

(然而,在我看來,你不應該這樣做我認爲這種情況是一個錯誤,揭示了斯威夫特的用於參照的功能語法。不足。我提交了一個bug報告,但無濟於事。)


只是爲了總結新#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(_:)。由於Objective-C不允許超載,因此只有其中一個被暴露,並且我們可以形成一個選擇器,僅用於的選擇器,因爲選擇器是一個Objective- C功能。但就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是一個類的方法,而不是一個實例方法,但它會正確地解決選擇,儘管如此,這是重要的。)

+0

我沒有安裝Xcode beta,但不會'#selector(test())'工作嗎? – Sulthan

+2

嗨@Sulthan,很高興收到你的來信。 - 不,這是解釋函數調用。根本沒有辦法直接提出「沒有參數的概念」的概念。這是一個漏洞;他們似乎已經提前完成了這個任務,並沒有像往常一樣思考... – matt

+0

我剛剛通過郵件討論「使用參數標籤命名函數」。引用是:_Zero參數函數引用仍然需要通過上下文類型信息消除歧義。因此,我認爲你找到了正確的解決方案。我也不喜歡這個語法,我更喜歡'test(Void)'這樣的東西。不幸的是,函數調用和函數描述符之間存在很多語法衝突。 – Sulthan