oc runtime
觀 :http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/ ?的總結:
1.調用方法的本質:
[receiver message]:
?---- objec_msgSend(receiver,selector)
如果有參數 ?------objec_msgSend(receiver,selector, arg1,arg2,...)
?
2. Runtime 的版本:modern(現在)和legacy ? ?::http://www.opensource.apple.com/source/
3. NSObject:
NSProxy:它是個抽象超類,它實現了一些消息轉發有關的方法,可以通過繼承它來實現一個其他類的替身類或是虛擬出一個不存在的類,說白了就是領導把自己展現給大家風光無限,但是把活兒都交給幕后小弟去干。
?
4. Runtime 函數
Runtime 系統是一個由一系列函數和數據結構組成,具有公共接口的動態共享庫。頭文件存放于/usr/include/objc目錄下。許多函數允許你用純C代碼來重復實現 Objc 中同樣的功能。雖然有一些方法構成了NSObject類的基礎,但是你在寫 Objc 代碼時一般不會直接用到這些函數的,除非是寫一些 Objc 與其他語言的橋接或是底層的debug工作。在Objective-C Runtime Reference中有對 Runtime 函數的詳細文檔。
id objc_msgSend(id self, SEL op, ...);
?
4.1SEL:
objc_msgSend函數第二個參數類型為SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區分方法的 ID,而這個 ID 的數據結構是SEL:
其實它就是個映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統的sel_registerName函數來獲得一個SEL類型的方法選擇器。
?typedef struct objc_selector *SEL;
?SEL實質:
工程中的所有的SEL組成一個Set集合,Set的特點就是唯一,因此SEL是唯一的。因此,如果我們想到這個方法集合中查找某個方法時,只需要去 找到這個方法對應的SEL就行了,SEL實際上就是根據方法名hash化了的一個字符串,而對于字符串的比較僅僅需要比較他們的地址就可以了,可以說速度 上無語倫比!!但是,有一個問題,就是數量增多會增大hash沖突而導致的性能下降(或是沒有沖突,因為也可能用的是perfect hash)。但是不管使用什么樣的方法加速,如果能夠將總量減少(多個方法可能對應同一個SEL),那將是最犀利的方法。那么,我們就不難理解,為什么 SEL僅僅是函數名了。
本質上,SEL只是一個指向方法的指針(準確的說,只是一個根據方法名hash化了的KEY值,能唯一代表一個方法),它的存在只是為了加快方法的查詢速度。這個查找過程我們將在下面討論。
我們可以在運行時添加新的selector,也可以在運行時獲取已存在的selector,我們可以通過下面三種方法來獲取SEL:
1. sel_registerName函數
2. Objective-C編譯器提供的@selector()
3. NSSelectorFromString()方法
4.2 id
typedef struct objc_object *id;
struct objec_object{
Class isa;
} ;
self指向了對象的首地址,而對象的首地址一般是isa變量,isa又是保存了對象的類對象的首地址!
4.3Classs:
| struct objc_class { ??? struct objc_class?super_class; ?/*父類*/ ??? const char *name;? ???????????????/*類名字*/ ??? long version;???????????????????/*版本信息*/ ??? long info;????????? ??????????????/*類信息*/ ??? long instance_size;????????????? ?/*實例大小*/ ??? struct objc_ivar_list *ivars;?????/*實例參數鏈表*/ ??? struct objc_method_list **methodLists;??/*方法鏈表*/ ??? struct objc_cache *cache; ??????????????/*方法緩存*/ ??? struct objc_protocol_list *protocols;???/*協議鏈表*/ }; ? |
?也就是說可以動態修改*methodLists的值來添加成員方法,這也是Category實現的原理,同樣解釋了Category不能添加屬性的原因。
任性的話可以在Category中添加@dynamic的屬性,并利用運行期動態提供存取方法或干脆動態轉發;或者干脆使用關聯度對象(AssociatedObject)
struct objc_ivar_list { int ivar_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_method_list { struct objc_method_list *obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; } 不知道你是否注意到了objc_class中也有一個isa對象,這是因為一個 ObjC 類本身同時也是一個對象,為了處理類和對象的關系,runtime 庫創建了一種叫做元類 (Meta Class) 的東西,類對象所屬類型就叫做元類,它用來表述類對象本身所具備的元數據。類方法就定義于此處,因為這些方法可以理解成類對象的實例方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關的元類。當你發出一個類似[NSObject alloc]的消息時,你事實上是把這個消息發給了一個類對象 (Class Object) ,這個類對象必須是一個元類的實例,而這個元類同時也是一個根元類 (root meta class) 的實例。所有的元類最終都指向根元類為其超類。所有的元類的方法列表都有能夠響應消息的類方法。所以當?[NSObject alloc]?這條消息發給類對象的時候,objc_msgSend()會去它的元類里面去查找能夠響應消息的方法,如果找到了,然后對這個類對象執行方法調用。
4.4Method
typedef struct objc_method *Method;
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; } 4.5 typeded struct objc_ivar *Ivar; struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; char *ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif } 4.6 IMP type id(*IMP)(id,SEL,...); 它就是一個函數指針,這是由編譯器生成的。當你發起一個 ObjC 消息之后,最終它會執行的那段代碼,就是由這個函數指針指定的。而?IMP?這個函數指針就指向了這個方法的實現。既然得到了執行某個實例某個方法的入口,我們就可以繞開消息傳遞階段,直接執行方法,這在后面會提到。 你會發現IMP指向的方法與objc_msgSend函數類型相同,參數都包含id和SEL類型。每個方法名都對應一個SEL類型的方法選擇器,而每個實例對象中的SEL對應的方法實現肯定是唯一的,通過一組id和SEL參數就能確定唯一的方法實現地址;反之亦然。 4.7Cache typedef struct objc_cache *Cache; struct objc_cache { unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; unsigned int occupied OBJC2_UNAVAILABLE; Method buckets[1] OBJC2_UNAVAILABLE; };Cache為方法調用的性能進行優化,通俗地講,每當實例對象接收到一個消息時,它不會直接在isa指向的類的方法列表中遍歷查找能夠響應消息的方法,因為這樣效率太低了,而是優先在Cache中查找。Runtime 系統會把被調用的方法存到Cache中(理論上講一個方法如果被調用,那么它有可能今后還會被調用),下次查找的時候效率更高。這根計算機組成原理中學過的 CPU 繞過主存先訪問Cache的道理挺像,而我猜蘋果為提高Cache命中率應該也做了努力吧。
?4.8 Property
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;
可以通過class_copyPropertyList?和?protocol_copyPropertyList方法來獲取類和協議中的屬性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)返回類型為指向指針的指針,哈哈,因為屬性列表是個數組,每個元素內容都是一個objc_property_t指針,而這兩個函數返回的值是指向這個數組的指針。
?id LenderClass = objc_getClass("Lender");
unsigned int outCount,i;
objc_property_t *properties = class_copyPropertyList(LenderClass,&outCount);
for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property)); } 消息:下面詳細敘述下消息發送步驟:
PS:這里說的分發表其實就是Class中的方法列表,它將方法選擇器和方法實現地址聯系起來。
?
方法中的隱藏參數
我們經常在方法中使用self關鍵字來引用實例本身,但從沒有想過為什么self就能取到調用當前方法的對象吧。其實self的內容是在方法運行時被偷偷的動態傳入的。
當objc_msgSend找到方法對應的實現時,它將直接調用該方法實現,并將消息中所有的參數都傳遞給方法實現,同時,它還將傳遞兩個隱藏的參數:
- 接收消息的對象(也就是self指向的內容)
- 方法選擇器(_cmd指向的內容)
?之所以說它們是隱藏的是因為在源代碼方法的定義中并沒有聲明這兩個參數。它們是在代碼被編譯時被插入實現中的。盡管這些參數沒有被明確聲明,在源代碼中我們仍然可以引用它們。
?
獲取方法地址
在IMP那節提到過可以避開消息綁定而直接獲取方法的地址并調用方法。這種做法很少用,除非是需要持續大量重復調用某方法的極端情況,避開消息發送泛濫而直接調用該方法會更高效。
?
?
?
?
?
轉載于:https://www.cnblogs.com/Ohero/p/4491717.html
總結
以上是生活随笔為你收集整理的oc runtime的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux tmux分屏工具
- 下一篇: C#使用Word中的内置对话框实例