@selector 是 Objective-C 時(shí)代的一個(gè)關(guān)鍵字,它可以將一個(gè)方法轉(zhuǎn)換并賦值給一個(gè) SEL 類(lèi)型,它的表現(xiàn)很類(lèi)似一個(gè)動(dòng)態(tài)的函數(shù)指針。在 Objective-C 時(shí) selector 非常常用,從設(shè)定 target-action,到自舉詢(xún)問(wèn)是否響應(yīng)某個(gè)方法,再到指定接受通知時(shí)需要調(diào)用的方法等等,都是由 selector 來(lái)負(fù)責(zé)的。在 Objective-C 里生成一個(gè) selector 的方法一般是這個(gè)樣子的:
-(void) callMe {
//...
}
-(void) callMeWithParam:(id)obj {
//...
}
SEL someMethod = @selector(callMe);
SEL anotherMethod = @selector(callMeWithParam:);
// 或者也可以使用 NSSelectorFromString
// SEL someMethod = NSSelectorFromString(@"callMe");
// SEL anotherMethod = NSSelectorFromString(@"callMeWithParam:");
一般為了方便,很多人會(huì)選擇使用 @selector,但是如果要追求靈活的話,可能會(huì)更愿意使用 NSSelectorFromString 的版本 -- 因?yàn)槲覀兛梢栽谶\(yùn)行時(shí)動(dòng)態(tài)生成字符串,從而通過(guò)方法的名字來(lái)調(diào)用到對(duì)應(yīng)的方法。
在 Swift 中沒(méi)有 @selector 了,取而代之,從 Swift 2.2 開(kāi)始我們使用 #selector 來(lái)從暴露給 Objective-C 的代碼中獲取一個(gè) selector。類(lèi)似地,在 Swift 里對(duì)應(yīng)原來(lái) SEL 的類(lèi)型是一個(gè)叫做 Selector 的結(jié)構(gòu)體。像上面的兩個(gè)例子在 Swift 中等效的寫(xiě)法是:
func callMe() {
//...
}
func callMeWithParam(obj: AnyObject!) {
//...
}
let someMethod = #selector(callMe)
let anotherMethod = #selector(callMeWithParam(_:))
和 Objective-C 時(shí)一樣,記得在 callMeWithParam 后面加上冒號(hào) (:),這才是完整的方法名字。多個(gè)參數(shù)的方法名也和原來(lái)類(lèi)似,是這個(gè)樣子:
func turnByAngle(theAngle: Int, speed: Float) {
//...
}
let method = #selector(turnByAngle(_:speed:))
最后需要注意的是,selector 其實(shí)是 Objective-C runtime 的概念,如果你的 selector 對(duì)應(yīng)的方法只在 Swift 中可見(jiàn)的話 (也就是說(shuō)它是一個(gè) Swift 中的 private 方法),在調(diào)用這個(gè) selector 時(shí)你會(huì)遇到一個(gè) unrecognized selector 錯(cuò)誤:
這是錯(cuò)誤代碼
private func callMe() {
//...
}
NSTimer.scheduledTimerWithTimeInterval(1, target: self,
selector:#selector(callMe), userInfo: nil, repeats: true)
正確的做法是在 private 前面加上 @objc 關(guān)鍵字,這樣運(yùn)行時(shí)就能找到對(duì)應(yīng)的方法了。
@objc private func callMe() {
//...
}
NSTimer.scheduledTimerWithTimeInterval(1, target: self,
selector:#selector(callMe), userInfo: nil, repeats: true)
最后,值得一提的是,如果方法名字在方法所在域內(nèi)是唯一的話,我們可以簡(jiǎn)單地只是用方法的名字來(lái)作為 #selector 的內(nèi)容。相比于前面帶有冒號(hào)的完整的形式來(lái)說(shuō),這么寫(xiě)起來(lái)會(huì)方便一些:
let someMethod = #selector(callMe)
let anotherMethod = #selector(callMeWithParam)
let method = #selector(turnByAngle)
但是,如果在同一個(gè)作用域中存在同樣名字的兩個(gè)方法,即使它們的函數(shù)簽名不相同,Swift 編譯器也不允許編譯通過(guò):
func commonFunc() {
}
func commonFunc(input: Int) -> Int {
return input
}
let method = #selector(commonFunc)
// 編譯錯(cuò)誤,`commonFunc` 有歧義
對(duì)于這種問(wèn)題,我們可以通過(guò)將方法進(jìn)行強(qiáng)制轉(zhuǎn)換來(lái)使用:
let method1 = #selector(commonFunc as ()->())
let method2 = #selector(commonFunc as Int->Int)