IOS學習 - 方法選取器Selector的相關使用

來源:互聯網
上載者:User

標籤:

問題的由來

Objective-C是一門動態語言,只要有可能,Objective-C總會使用動態方式來解決問題,比如它儘可能的將編譯和串連要做的事延遲到運行時,所以它需要一個強大的運行時系統(runtime)來執行編譯好的代碼。runtime的角色類似於Objective-C語言的作業系統,是Objective-C的靈魂。Objective-C的很多特性都是基於這個強大的運行時的,是十分值得去學習理解的。

動態語言Objective-C很常見的一個訊息發送語句(比較準確的說是訊息發送,在不混淆的情況下我還是喜歡說方法調用):

1 [receiver message];

 

在編譯時間會轉化成:

1 objc_msgSend(receiver, selector);

 

然後再進行編譯的後續工作。

事實上還有幾個類似的方法:

1 objc_msgSend_stret(傳回值是結構體)2 objc_msgSend_fpret(傳回值是浮點型)3 objc_msgSendSuper(調用父類方法)4 objc_msgSendSuper_stret(調用父類方法,傳回值是結構體)

 

編譯時間只是確定了要向確定的執行個體發特定的訊息,並沒有去驗證訊息的實現(IMP),IDE Xcode會做一些基本的驗證,但是涉及到@selector()得來的方法,僅僅只是警告,這裡也經常發生崩潰。

編譯時間不驗證訊息實現,訊息也只有在運行時才會具體發送。

註:個人理解是,訊息的實現叫方法,方法的傳遞過程叫訊息。

 

Selector/SEL是什麼

Selector/SEL又叫方法選取器,SEL在objc.h中是這樣定義的,而“@selector()”是取得一個SEL指標。說白了,方法選取器僅僅是一個char *指標,表示它所代表的是方法的名字:

1 typedef struct objc_selector *SEL;2 SEL selector = @selector(message); //@selector不是函數調用,只是給編譯器的一個提示3 NSLog (@"%s", (char *)selector); //print message

Objective-C在編譯的時候,會根據方法的名字,產生一個唯一的用來區分這個方法的ID,這個ID就是SEL類型的。需要注意的是,只要方法的名字相同,那麼它們的ID都是相同的,所以Objective-C,沒有重載這個概念,所以你會在Objective-C中看到一個很奇怪的方法命名現象:

1 //-(void)setWidth:(int)width;            這個函數被改寫為下面的函數2 -(void)setWidthIntValue:(int)width;3 4 //-(void)setWidth:(double)width;      這個函數被改寫為下面的函數5 -(void)setWidthDoubleValue:(double)width            

這裡先提一下,擷取了SEL後,若想向某一對象發送訊息,可以使用performSelecor:

1 [receiver performSelecor:@selector(message)];

 但一般建議先用respondsToSelector檢查對象是否可以響應這個訊息。

 

寫到這裡,你可能感覺這個SEL和C/C++中的函數指標很像,那麼你可能要失望了,因為IMP和函數指標更像。

IMP是”implementation”的縮寫,它是objetive-C 方法(method)實現代碼塊的地址,可像C函數一樣直接調用。通常情況下我們是通過[object method:parameter]或objc_msgSend()的方式向對象發送訊息,然後Objective-C運行時(Objective-C runtime)尋找匹配此訊息的IMP,然後調用它。

IMP也是一個指標,它指向的是方法的地址,而SEL只是一個ID,用於runtime從方法表中找到對應的IMP。SEL是方法的ID,IMP是方法的實現。IMP和C/C++中函數指標的不同是,函數指標的值,在編譯時間是確定的,而IMP也是在運行時才確定。關於IMP的部分後幾天總結一下再發上來,有興趣的也可以自己先搜一下。

 

 selector主要用於兩個對象之間進行松耦合的通訊。這種方法很多開發環境用到,比如GTK,Delphi。基本上整個Cocoa庫之間對象,控制之間通訊都是在這個基礎構建的。Selector是Objective-C動態性實現的十分重要的一部分。

 

Selector/SEL的儲存、尋找

剛說到,編譯器會在編譯時間根據方法名為方法產生唯一的ID(SEL),而這些SEL構成了若一個集合,由runtime(運行時)維護。runtime為所有正在使用的類和執行個體維護著這些集合,並且為了減少動態訊息帶來的時間成本,runtime還為他們維護著一個SEL的cache。方法集合和cache是通過最佳化過的Hash實現的,通過散列可以可以加快查詢速度。

iOS中的obj都繼承於NSObject,在NSObjcet中存在一個Class的isa指標。

1 @interface NSObject  {2     Class isa  OBJC_ISA_AVAILABILITY;3 }

 

 再來看Class:

 1 typedef struct objc_class *Class; 2 struct objc_class { 3     Class isa; //指向元類metaclass 4  5     Class super_class ; //指向其父類superclass 6     const char *name ; //類名 7     long version ; //類的版本資訊,初始化預設為0,可以通過runtime函數class_setVersion和class_getVersion進行修改、讀取 8     long info; //一些標識資訊,如CLS_CLASS(0x1L)表示該類為普通class,其中包含對象方法和成員變數;CLS_META (0x2L) 表示該類為元類metaclass,其中包含類方法; 9     long instance_size ; //該類的執行個體變數大小(包括從父類繼承下來的執行個體變數);10     struct objc_ivar_list *ivars; //用於儲存每個成員變數的地址11     struct objc_method_list **methodLists ; //與 info 的一些標誌位有關,如CLS_CLASS(0x1L),則儲存物件方法,如CLS_META(0x2L),則儲存類方法;12     struct objc_cache *cache; //指向最近使用的方法的指標,用於提升效率;13     struct objc_protocol_list *protocols; //儲存該類遵守的協議14 }

 

可以看到,任意的類的isa成員(從NSObject繼承下來的)中儲存了該類的很多資訊。

Class isa:指向metaclass,也就是靜態Class。

Class super_class:指向父類,如果這個類是根類,則為NULL。

下面是isa和superclass的指向關係:

isa指標:

一個Obj執行個體中的isa會指向普通的Class,這個Class中儲存普通成員變數和對 象方法(“-”開頭的方法)。

普通Class中的isa指標指向靜態Class,靜態Class中儲存static類型成員變數和類方法(“+”開頭的方 法)。

所有元類metaclass中isa指標均指向根元類root metaclass。根元類root metaclass的isa指標也指向自身。

super_class:

普通的Class的super_class指向其父類普通的Class。

元類metaclass的super_class指向其父類元類metaclass。

根元類root metaclass的super_class指向根類普通的Class,根類普通Class的super_class為nil。(順便提一下,Objective-C中,nil、Nil、NULL的值相同)

 

下面我們就來看看具體訊息發送之後是怎麼來動態尋找對應的方法的。

首先,編譯器將代碼[receiver message];轉化為objc_msgSend(receiver , @selector (message));,在objc_msgSend函數中。首先通過receiver的isa指標找到receiver對應的class。在Class中先去cache中通過SEL尋找對應函數method,若 cache中未找到。再去methodList中尋找,若methodlist中未找到,則取superClass中尋找。若能找到,則將method加 入到cache中,以方便下次尋找,並通過尋找到的IMP跳轉到對應的函數中去執行。

 

Selector相關用法

-(BOOL) isKindOfClass: classObj //判斷是否是某個類或其子類的執行個體

-(BOOL) isMemberOfClass: classObj //判斷是否是某個類的執行個體

-(BOOL) respondsToSelector: selector //判斷是否有以某個名字命名的方法(被封裝在一個selector的對象裡傳遞)

+(BOOL) instancesRespondToSelector: selector //判斷執行個體是否有以某個名字命名的方法. 和上面一個不同之處在於, 前面這個方法可以用在執行個體和類上,而此方法只能用在類上.

-(id) performSelector: selector //發送訊息(調用方法)

………………今天白天補吧,這都一點半了………………

…………………………………T,T……………………………………

IOS學習 - 方法選取器Selector的相關使用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.