第一章 神奇
猜猜哪個btnTapped函數(shù)會被調(diào)用础米?
import UIKit
class DemoViewController : UIViewController {
override func viewDidLoad() {
let view = DemoView()
let btn = view.btn;
self.view.addSubview(btn!)
}
@objc func btnTapped(_ sender: UIButton) {
print("print: surprise!")
}
}
class DemoView {
var btn : UIButton!
init() {
btn = UIButton()
btn.frame = CGRect(x: 110, y: 70, width: 100, height: 44)
btn.backgroundColor = UIColor.blue
btn.setTitle("Press me", for: .normal)
btn.setTitle("I'm Pressed", for: .highlighted)
btn.addTarget(self, action: #selector(DemoView.btnTapped(_:)), for: .touchUpInside)
}
@objc func btnTapped(_ sender: UIButton) {
print("print: demo button is tapped")
}
}
答案是:surprise!
第二章 解惑
正確的寫法應(yīng)該用singleton模式來實現(xiàn)DemoView,如下。
import UIKit
class DemoViewController : UIViewController {
override func viewDidLoad() {
let view = DemoView._instance
let btn = view.btn;
self.view.addSubview(btn!)
}
@objc func btnTapped(_ sender: UIButton) {
print("print: surprise!")
}
}
class DemoView {
var btn : UIButton!
static let _instance = DemoView()
private init() {
btn = UIButton()
btn.frame = CGRect(x: 110, y: 70, width: 100, height: 44)
btn.backgroundColor = UIColor.blue
btn.setTitle("Press me", for: .normal)
btn.setTitle("I'm Pressed", for: .highlighted)
btn.addTarget(self, action: #selector(DemoView.btnTapped), for: .touchUpInside)
}
@objc func btnTapped(_ sender: UIButton) {
print("print: demo button is tapped")
}
}
<b>洞察:</b>
- Selector是runtime時延遲動態(tài)綁定亏掀,#selector糖里面只是為了compiler幫助檢查prototype正確性(對以前的字符串寫法”btnTapped:”無法在編譯期檢查問題的優(yōu)化)忱反,所以即使寫成DemoView.btnTapped,也只是告訴compiler這個原型參考函數(shù)滤愕,而并非Selector在runtime時真正選取的函數(shù)温算。
因為函數(shù)選取是在Obj-C名字空間里做,所以需要用@objc修飾需要暴露給Obj-C的函數(shù)间影。 - addTarget的第一個參數(shù)是Obj-C消息機制的receiver對象注竿,把selector選取的函數(shù)發(fā)送給對象,其實就是“對象的方法調(diào)用”魂贬。
- self也是運行時選取巩割,所以才會出現(xiàn)神奇的現(xiàn)象,即調(diào)用了DemoViewController而不是DemoView的btnTapped函數(shù)随橘。
也因此,如果把上面錯誤寫法中的btn.addTarget(self改為btn.addTarget(DemoView.self锦庸,即強行要求調(diào)用DemoView對象(instance of the class)的函數(shù)机蔗,運行時點擊按鈕就會得到錯誤,unrecognized selector +[DemoView btnTapped:]甘萧。 - BTW萝嘁,可以看到,因為swift的lazy initialization特性扬卷,所以實現(xiàn)singleton異常簡單牙言。
第三章 祛魅
Debug容易成為一門玄學(xué)。以下是國內(nèi)外網(wǎng)友分享的一些“迷信“怪得。
#selector選擇的類必須繼承NSObject咱枉,也就是要寫成class DemoView : NSObject
實測:假。原型要寫成btnTapped(_:)徒恋。
實測:假蚕断。
第四章 優(yōu)雅
通過extension Selector可以寫的更加優(yōu)雅。
import UIKit
class DemoViewController : UIViewController {
override func viewDidLoad() {
let view = DemoView._instance
let btn = view.btn;
self.view.addSubview(btn!)
}
@objc func btnTapped(_ sender: UIButton) {
print("print: surprise!")
}
}
class DemoView {
var btn : UIButton!
static let _instance = DemoView()
private init() {
btn = UIButton()
btn.frame = CGRect(x: 110, y: 70, width: 100, height: 44)
btn.backgroundColor = UIColor.blue
btn.setTitle("Press me", for: .normal)
btn.setTitle("I'm Pressed", for: .highlighted)
btn.addTarget(self, action: .btnTapped, for: .touchUpInside)
}
@objc func btnTapped(_ sender: UIButton) {
print("print: demo button is tapped")
}
}
private extension Selector {
static let btnTapped = #selector(DemoView.btnTapped)
}
<b>解讀:</b>
action:后面直接用點號的寫法是一種糖入挣,compiler看起來就是Selector.btnTapped亿乳,也正是最下方的extension代碼所實現(xiàn)的。
From painful to painless.