Swift中的Selector

BigNerdCoding發表於2016-04-05

Swift中的Selector

前言

Selector作為一個在很多Objective-C設計模式中的重要組成部分,Swift為了保證部分介面的一致性依然保留了這一概念。這篇文章時我在學習這部分內容時的遇到問題的一些總結。

雖然Swift中依然保留了對Selector的支援。但是在某些地方我們可以採用更為安全的方式來實現Objective-C中對應的部分。例如:respondsToSelector:performSelector:可以分別使用協議型別的可選鏈和閉包進行替換。

//某個協議 *respondsToSelector:*

//Objcctive-C版本 
if(self.delegate.respondsToSelector(Selector("HanggeSwiftMenuWillAnimateClose:"))){
    self.delegate.HanggeSwiftMenuWillAnimateClose(self)
}

//Swift版本
self.delegate?.HanggeSwiftMenuWillAnimateClose(self)

但是還是很多像timer和target/action設計模式一樣的地方大量的使用了Selector。下面我們看下這部分的處理。

Swift2.2之前的情況

在Swift2.2版本之前,selector通過一個簡單的字串常量來傳遞。因為字串是我們開發人員手寫而且還沒有自動補全這增加的程式出差的可能性。

    let button = UIButton(type: .System)
    button.addTarget(self, action: Selector(“buttonTapped:”), forControlEvents: .TouchUpInside)
    …
    func buttonTapped(sender: UIButton){ }
    
一個很好的函式命名習慣和風格就是使用控制元件物件的名稱作為函式的字首。在上面的示例中,函式名稱buttonTapped:對應的就是按鍵buttontapped事件。並且記住要傳遞唯一一個型別正確的sender引數,因為你有這個物件但是不用,總是好過你需要這個物件但是沒有要好。

總體上來說並不是很難,但是這裡又一些問題需要我們注意:

Selector的可用性:通過selector引出的方法需要暴露給ObjC執行時。如果是繼承自NSObject的類那麼就已經實現了這個特性,但是如果是純Swift的類你需要在函式的宣告前面加上@objc來進行實現。並且該函式訪問級別最起碼應該是internal,因為private是無法暴露給執行時的。

Selector的名字:因為selectors是一個ObjC中的東西遵循ObjC的方法命名規則,每一個引數都有一個冒號(:)。例如,名為test()的selector就是”test”,test(this: String)的selector就是”test:”,test(this: String, andThat: Int)就是”test:andThat:”。

Swift2.2中的改進

在Swift2.2中selector已經變得更加安全了。它使用#selector這種語法來實現Selector,這避免了之前使用字串可能帶來的手動輸入錯誤。因為該語法會讓編譯器會對方法進行檢查,不存在的方法是無法通過編譯的,這個在之前做法中是做不到的。

button.addTarget(self, action: #selector(ViewController.buttonTapped(_:)), forControlEvents: .TouchUpInside)

當你一眼掃過去會發現程式碼比較長也不是很容易閱讀。尤其是如果你的程式碼中有很多的ViewController型別的類並且在程式碼中多次使用同一個Selector,多次的拷貝/複製這麼長的程式碼或者是修改是不是想想就很麻煩啊?一種解決方法是:將所有Selector整理放在同一個地方,進行統一的編輯和引用。

private struct Action {
    static let buttonTapped = 
        #selector(ViewController.buttonTapped(_:))
}
...
button.addTarget(self, action: Action.buttonTapped,       
    forControlEvents: .TouchUpInside)
    

我們將所有的Selector作為靜態常量放在Action結構裡面。這裡之所以將Action宣告為private是為了防止其它檔案裡面也定義了一個這樣的結構導致編譯時候的重定義。

另一種更為語法糖一些的解決方法就是對Selector進行extension

private extension Selector {
    static let buttonTapped = 
        #selector(ViewController.buttonTapped(_:))
}
...
button.addTarget(self, action: .buttonTapped, 
    forControlEvents: .TouchUpInside)
    

利用語法特性,我們不需要使用Selector.buttonTapped,從而使程式碼更簡潔一些,也更裝X一些。

相關文章