Swift 中的 Selector

由于種種原因鳞尔,簡(jiǎn)書等第三方平臺(tái)博客不再保證能夠同步更新彼念,歡迎移步 GitHub:https://github.com/kingcos/Perspective/狞尔。謝謝斯议!

Selectors in Swift

  • Info:
  • Swift 3.0
  • Xcode 8.2.1
  • macOS 10.12.4 beta (16E144f)

前言

今天是大年初四(捂臉:提筆的時(shí)候是初一),總算過農(nóng)歷新年了乞旦,總算可以歇一歇了贼穆。越來越感慨時(shí)間過得飛快,計(jì)劃總是趕不上變化兰粉。寒假倒計(jì)時(shí) 20 天故痊,卻有很多事都還沒有完成。玖姑。

常用純代碼來開發(fā)的同學(xué)都應(yīng)該比較熟悉這個(gè)方法:

func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControlEvents)

Selector 源自 Objective-C愕秫,例如 SEL 類型,以及 @selector() 方法選擇器客峭。Swift 中也兼容了這個(gè)概念豫领,不過隨著 Swift 的迭代,Selector 的一些寫法也出現(xiàn)了很大的變化舔琅。比較遺憾的是,官方文檔對(duì)于 Selector 沒有介紹洲劣。

Selector in Xcode Documentation & API Reference

因此只能自己總結(jié)一下 Swift 3.0 中的 Selector备蚓,便有利于自己理解,也便于以后的參考囱稽。注:以下 Demo 中的 cyanButton 是用 StoryBoard 拖拽的郊尝。

Selector 類型

Swift 中的 Selector 類型其實(shí)就是 Objective-C 中的 SEL 類型。在 Swift 中战惊,Selector 的本質(zhì)是結(jié)構(gòu)體流昏。常用的構(gòu)造 Selector 類型變量的方法有以下幾種:

  • public init(_ str: String)

類似 Objective-C 中的 NSSelectorFromString,Swift 中的 Selector 也可以使用字符串來構(gòu)造:

@IBOutlet weak var cyanButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    cyanButton.addTarget(self,
                         action: Selector("cyanButtonClick"),
                         for: .touchUpInside)
}

func cyanButtonClick() {
    print(#function)
}
  • #selector()

通過字符串構(gòu)造 Selector 變量是一種方法吞获,但是當(dāng)在上例中 Xcode 會(huì)提示這樣的警告:「Use '#selector' instead of explicitly constructing a 'Selector'」况凉。即使用 #selector() 代替字符串明確構(gòu)造 Selector。

@IBOutlet weak var cyanButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    cyanButton.addTarget(self,
                         action: #selector(ViewController.cyanButtonClick),
                         for: .touchUpInside)
}

func cyanButtonClick() {
    print(#function)
}

#selector() 的好處是不再需要使用字符串來構(gòu)造各拷。因?yàn)楫?dāng)使用字符串構(gòu)造時(shí)刁绒,若傳入的字符串沒有對(duì)應(yīng)的方法名,那么程序在執(zhí)行時(shí)就會(huì)直接崩潰:「unrecognized selector sent to instance」烤黍。

若當(dāng)前作用域構(gòu)造 Selector 的方法名唯一時(shí)知市,可以直接使用方法名傻盟,而省略作用域。

cyanButton.addTarget(self,
                     action: #selector(cyanButtonClick),
                     for: .touchUpInside)

若是 Swift 中的私有方法嫂丙,則必須賦予其 Objective-C 的 runtime(運(yùn)行時(shí))娘赴。即在方法名前加上 @objc

@IBOutlet weak var cyanButton: UIButton!
@IBOutlet weak var anotherCyanButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()
    cyanButton.addTarget(self,
                         action: #selector(ViewController.cyanButtonClick(_:)),
                         for: .touchUpInside)

    // 當(dāng)前作用域 cyanButtonClick 存在沖突,不能直接使用方法名
    //「Ambiguous use of 'cyanButtonClick'」
    // anotherCyanButton.addTarget(self,
                                action: #selector(cyanButtonClick),
                                for: .touchUpInside)
}

// 無參方法
func cyanButtonClick() {
    print(#function)
}

// 有參私有方法
@objc private func cyanButtonClick(_ button: UIButton) {
    let btnLabel = button.titleLabel?.text ?? "nil"
    print(btnLabel)
    print(#function)
}

當(dāng)遇到上述存在歧義的相同方法名時(shí)跟啤,也可以使用強(qiáng)制類型轉(zhuǎn)換來解決:

@IBOutlet weak var cyanButton: UIButton!
@IBOutlet weak var anotherCyanButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    let methodA = #selector(cyanButtonClick as () -> ())
    let methodB = #selector(cyanButtonClick as (UIButton) -> ())

    cyanButton.addTarget(self,
                         action: methodA,
                         for: .touchUpInside)
    anotherCyanButton.addTarget(self,
                                action: methodB,
                                for: .touchUpInside)
}

func cyanButtonClick() {
    print(#function)
}

@objc private func cyanButtonClick(_ button: UIButton) {
    let btnLabel = button.titleLabel?.text ?? "nil"
    print(btnLabel)
    print(#function)
}
  • #selector() & Seletcor("")

通過上面的 Demo筝闹,也可以看出 #selector() 更加安全、清晰腥光,但是 Seletcor("") 并不是一無是處关顷。當(dāng)我們需要調(diào)用標(biāo)準(zhǔn)庫中的私有方法時(shí),只能通過字符串來構(gòu)造武福。

為了方便測(cè)試议双,此處自定義了一個(gè) CustomViewController。其中帶有私有方法:@objc private func privateFunc() 以及 func defaultFunc()捉片。此處使用的 ViewController 繼承自 CustomViewController

CustomViewController.swift

class CustomViewController: UIViewController {

    @objc private func privateFunc() {
        print(#function)
    }

    func defaultFunc() {
        print(#function)
    }

}

ViewController.swift

class ViewController: CustomViewController {

    @IBOutlet weak var cyanButton: UIButton!
    @IBOutlet weak var anotherCyanButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        cyanButton.addTarget(self,
                             action: #selector(defaultFunc),
                             for: .touchUpInside)
        anotherCyanButton.addTarget(self,
                                    action: Selector("privateFunc"),
                                    for: .touchUpInside)
    }

}

因?yàn)楦割惖乃接蟹椒▽?duì)子類來說是不可見的平痰,直接使用 #selector() 無法通過編譯,但這個(gè)方法確實(shí)存在伍纫,所以這里只能使用字符串來構(gòu)造 Selector宗雇。

當(dāng)然這里 Xcode 會(huì)提示警告,但仍然可以編譯通過并運(yùn)行莹规,所以這并不是官方提倡的行為赔蒲。這是我在將系統(tǒng)邊緣返回改寫全屏返回時(shí),發(fā)現(xiàn)私有的 handleNavigationTransition: 方法不能通過 #selector()良漱,因此使用了字符串代替舞虱。

Syntax Sugar

配合 Swift 的 Extension,可以使用其管理當(dāng)前控制器的所有 Selector:

import UIKit

fileprivate extension Selector {
    static let redButtonClick = #selector(ViewController.redButtonClick(_:))
    static let cyanButtonClick = #selector(ViewController.cyanButtonClick)
}

class ViewController: CustomViewController {

    @IBOutlet weak var cyanButton: UIButton!
    @IBOutlet weak var redButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        cyanButton.addTarget(self,
                             action: .cyanButtonClick,
                             for: .touchUpInside)
        redButton.addTarget(self,
                                    action: .redButtonClick,
                                    for: .touchUpInside)
    }

    func cyanButtonClick() {
        print(#function)
    }

    func redButtonClick(_ button: UIButton) {
        let btnLabel = button.titleLabel?.text ?? "nil"
        print(btnLabel)
        print(#function)
    }

}

getter & setter

Swift 3.0 中加入了 Selector 引用變量(不可為常量)的 getter 和 setter 方法:

class Person: NSObject {
    dynamic var firstName: String
    dynamic let lastName: String
    dynamic var fullName: String {
        return "\(firstName) \(lastName)"
    }

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
}


fileprivate extension Selector {
    static let firstNameGetter = #selector(getter: Person.firstName)
    static let firstNameSetter = #selector(setter: Person.firstName)
}

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末母市,一起剝皮案震驚了整個(gè)濱河市矾兜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌患久,老刑警劉巖椅寺,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蒋失,居然都是意外死亡返帕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門高镐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溉旋,“玉大人,你說我怎么就攤上這事嫉髓」劾埃” “怎么了邑闲?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)梧油。 經(jīng)常有香客問我苫耸,道長(zhǎng),這世上最難降的妖魔是什么儡陨? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任褪子,我火速辦了婚禮,結(jié)果婚禮上骗村,老公的妹妹穿的比我還像新娘嫌褪。我一直安慰自己,他們只是感情好胚股,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布笼痛。 她就那樣靜靜地躺著,像睡著了一般琅拌。 火紅的嫁衣襯著肌膚如雪缨伊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天进宝,我揣著相機(jī)與錄音刻坊,去河邊找鬼。 笑死党晋,一個(gè)胖子當(dāng)著我的面吹牛谭胚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隶校,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼漏益,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了深胳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤铜犬,失蹤者是張志新(化名)和其女友劉穎舞终,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體癣猾,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡敛劝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纷宇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夸盟。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖像捶,靈堂內(nèi)的尸體忽然破棺而出上陕,到底是詐尸還是另有隱情桩砰,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布释簿,位于F島的核電站亚隅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏庶溶。R本人自食惡果不足惜煮纵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望偏螺。 院中可真熱鬧行疏,春花似錦、人聲如沸套像。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凉夯。三九已至货葬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劲够,已是汗流浹背震桶。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留征绎,地道東北人蹲姐。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像人柿,于是被迫代替她去往敵國和親柴墩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容