本文檔版權歸NickTang所有,沒有本人書面或電子郵件允許,不許轉載,摘錄,發表。多謝!
在Objective-C語言中, 選擇器(selector) 有兩層含義,在源代碼中,它指代一個函數名稱,在編譯期間,它被一個唯一的標記符替代。編譯後的選擇器被替換成一個SEL類型
. 所有具有同一個名稱的函數具有同一個選擇器。你可以使用選擇器調用一個對象的方法--這是Cocoa中的目標-動作設計模式的最基本實現方式。
函數和選擇器
爲了效率,編譯後的代碼不再使用函數名稱。作爲替代,編譯器把所有的函數名稱保存在一個表中,然後爲每一個函數名稱聲稱一個唯一的標識,並在運行的時候使用這個標識作爲函數的替代。運行環境包裝每一個標識是唯一的:同樣名稱的函數具有相同的選擇器,不同的函數的選擇器絕對不同。
SEL和@selector
編譯後的選擇器使用一個特別的類型,SEL,以區別與其他數據類型。你必須使用一個函數來付值給一個
SEL變量。
@selector()指示在編譯期間的選擇器。下面的例子演示了使用函數
setWidth:height:來付值給SEL類型的變量
setWidthHeight
:
SEL setWidthHeight; |
setWidthHeight = @selector(setWidth:height:); |
在編譯期間,使用@selector()來直接設置SEL變量是很有效的。但是,有些情況下,你可能需要在運行期間使用一個字符串來轉換成一個選擇器,這個時候,你需要使用
NSSelectorFromString
函數:
setWidthHeight = NSSelectorFromString(aBuffer); |
相反方向的轉換也是有可能需要的。函數 NSStringFromSelector就是做這個事情的:
NSString *method; |
method = NSStringFromSelector(setWidthHeight); |
函數和選擇器
編譯後的選擇器指代函數的名稱,而不是函數的實現。例如,一個類中的displa函數在其他類中可能有一個相同名字的函數,那麼這就是多態性和動態綁定的本質;它可以讓你對不同的類發送相同的消息。如果每一個函數的實現都不同,那麼一個消息可能會有不同的函數調用。
具有相同名稱的類函數和實例函數具有相同的選擇器。但是,由於它們在不同的作用域中,所以這兩種情況不會找出衝突,一個類可以擁有一個display實例函數的同時擁有一個display類函數。
函數的返回類型和參數類型
消息必須通過選擇器才能轉化爲對函數實現的調用,因此一個消息對所有的相應的函數都會作爲相同的選擇器使用。消息通過選擇器來了解函數的參數類型和返回類型。故此,除了是接受者是靜態指定的,動態綁定需要所有的同一個名字的函數必須具有相同的函數類型和返回類型。(由於編譯器會對靜態接受者進行檢查,所以它是這個規則的一個例外)
雖然相同的類方法和實例方法是有同一個選擇器,但是它們可以有不同的參數類型和返回類型。
運行期驗證消息
定義在類NSObject中的函數performSelector:,
performSelector:withObject:和
performSelector:withObject:withObject:
使用一個SEL作爲參數。這三個函數最後都轉換爲相應的函數調用,例如:
[friend performSelector:@selector(gossipAbout:) |
withObject:aNeighbor]; |
和下面的代碼相等:
[friend gossipAbout:aNeighbor]; |
這三個函數使得在運行期對消息和消息接受者驗證稱爲可能,並且在消息的任何部分都可以使用變量:
id helper = getTheReceiver(); |
SEL request = getTheSelector(); |
[helper performSelector:request]; |
在上面的例子中,接收者(helper
)是在運行期決定的 (通過函數getTheReceiver調用決定
), 並且接受者的函數(request
) 也是通過運行期決定的 。
注: performSelector:等一干函數返回一個
id類型的變量。如果函數返回一個其他的類型,必須通過轉換來變成合適的方式(當然,不是所有的類型都能被轉換,函數應該返回一個指針或者相似的類型)。
避免消息錯誤
如果一個接受者接到一個不在函數列表中的消息,就會發生一個錯誤。這個錯誤和調用一個不存在的函數的錯誤一樣。不過由於消息是在運行期發生的,所以這個錯誤直到程序運行的時候纔會發生發生。
這個錯誤在接受者和消息都是固定的時候是很容易避免的。由於是你寫的程序,所以你可以保證接受者是能夠對消息做出響應的。如果接受者是固定的,那麼編譯器可以爲你做這樣的檢測的。
不過,如果消息接受者和消息本身是動態的,那麼這個檢驗工作就要推遲到運行期了。定義在NSObject類中的函數respondsToSelector:就是爲了測試一個接受者是否響應某個消息。它接受一個函數選擇器作爲參數,返回接受者能否響應這個選擇器:
if ( [anObject respondsToSelector:@selector(setOrigin::)] ) |
[anObject setOrigin:0.0 :0.0]; |
else |
fprintf(stderr, "%s can’t be placed\n", |
[NSStringFromClass([anObject class]) UTF8String]); |
respondsToSelector:所作的運行期檢測是很重要的,特別是你發消息到不是你所控制的類中的時候。例如,你發送一個消息到一個可以通過變量設置的接受者得時候,你必須通過這個函數來確認接受者是否實現了這個函數。
注: 一個對象可以前轉一個消息到其他的接受者,對於消息的發送方看到的是這個對象處理了這個消息。前參考Objective-C
Runtime Programming Guide“中的 “Message
Forwarding”