函數(shù)和函數(shù)指針調(diào)用時候的區(qū)別:
區(qū)別:運行效率不同 ,直接調(diào)用函數(shù)指針運行效率高(ps:大量數(shù)據(jù)的時候)
?原因:調(diào)用函數(shù)的時候番挺,由于runtime機制,通過方法objc_msgSend() 把函數(shù)的調(diào)用對象和方法名發(fā)送出去
根據(jù)對象名找到對象類存儲的函數(shù)函數(shù)列表MethordList,再根據(jù)方法名找到MethordList 中的函數(shù)指針method_imp吗浩,再根據(jù)函數(shù)指針調(diào)用響應(yīng)函數(shù)
實現(xiàn)原理
任意一個NSObject的對象都有一個屬性isa 建芙,指向?qū)ο髮?yīng)的Class
@interface NSObject ?{
? ?Class isa ?OBJC_ISA_AVAILABILITY;
}
根據(jù)對象調(diào)用即可拿到對應(yīng)的Class ,Class 是純C的結(jié)構(gòu)體
typedef struct objc_class *Class;
struct objc_class {
? ?Class isa; // 指向metaclass
?? Class superclass; ?// 指向父類Class
? ?const char *name; ?// 類名
uint32_t version; ?// 類的版本信息
uint32_t info; ? ? ? ?// 一些標識信息懂扼,標明是普通的Class還是metaclass
uint32_t instance_size; ? ? ? ?// 該類的實例變量大小(包括從父類繼承下來的實例變量);
struct old_ivar_list *ivars; ? ?//類中成員變量的信息
struct old_method_list **methodLists; ? ?類中方法列表
Cache cache; ? ?查找方法的緩存禁荸,用于提升效率
struct old_protocol_list *protocols; ?// 存儲該類遵守的協(xié)議
}
由上面的代碼可以看出Class是一個結(jié)構(gòu)體指針,只想objc_class 結(jié)構(gòu)體阀湿。在object_class中存放著methodLists赶熟,所以我們可以根據(jù)Class找到方法列表
下面我們來看看怎么從methodLists中找到對應(yīng)的函數(shù)指針
struct old_method_list {
? ?void *obsolete; ? ? ? ?//廢棄的屬性
? ?int method_count; ? ?//方法的個數(shù)
? ?/* variable length structure */
? ?struct old_method method_list[1]; ? ?方法的首地址
};
struct old_method {
? ?SEL method_name; ? ?//方法對應(yīng)的SEL
? ?char *method_types; ? ? ? ?//方法的類型
? ?IMP method_imp; ? ? ? ?//方法對應(yīng)的函數(shù)指針
};
對于old_method_list結(jié)構(gòu)體,他存儲了old_method方法個數(shù)和方法首地址陷嘴。我們可以把他當做一個可變長度的old_method數(shù)組映砖。
注:
method_list[1],數(shù)組的大小怎么會是1呢灾挨?由于數(shù)組的大小是不定的邑退,不同的類對應(yīng)的不同的方法個數(shù)。所以定義時只存儲首地址劳澄,在實際使用過程中再擴展長度
對于old_method結(jié)構(gòu)體地技,他由SEL,type秒拔,IMP三個成員組成莫矗。由此可知,我們只要在method_list中找到了old_method即可拿到函數(shù)指針I(yè)MP砂缩。下面是查找的代碼:
static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) {
? ?int i;
? ?if (!mlist) return nil;
? ?for (i = 0; i < mlist->method_count; i++) {
? ? ? ?old_method *m = &mlist->method_list[i];
? ? ? ?if (m->method_name == sel) {
? ? ? ? ? ?return m;
? ? ? ?}
? ?}
? ?return nil;
}
1. 查找函數(shù)是個內(nèi)聯(lián)函數(shù)作谚,傳入old_method_list和SEL,返回old_method
2. 首先對old_method_list數(shù)組判空庵芭,如果為空妹懒,返回nil
3. 遍歷old_method_list數(shù)組,根據(jù)SEL匹配双吆,找到old_method
4眨唬、函數(shù)調(diào)用的性能優(yōu)化
4.1 ?SEL的使用
說明:蘋果官方對SEL的解釋是:一種不透明的類型滔悉,它代表著一個方法選擇器。SEL本質(zhì)其實是一個int類型的地址单绑,指向存儲的方法名。對于每一個類曹宴,都會分配一塊特殊空空間搂橙,專門存儲類中的方法名,SEL就是指向?qū)?yīng)方法名的地址笛坦。由于方法名字符串是唯一的区转,所以SEL也是唯一的。
類型:(int)型版扩,原因:方便效率高
4.2 cache(快速緩存區(qū))
我們來仔細分析一下函數(shù)的調(diào)用過程:
obj->isa->methodLists
old_method->method_imp
1. 由于isa是obj的成員變量废离,methodLists是isa的成員變量,所以用obj可以直接拿到methodLists
2. 由于method_imp是old_method的成員變量礁芦,所以用old_method可以直接拿到method_imp 所以函數(shù)調(diào)用過程的主要時間消耗在methodLists中查找old_method蜻韭。cache就是用來優(yōu)化這個查找過程的。我們可以把cache簡單當成一個哈希表柿扣,key是SEL肖方,Value是old_method。由此可知未状,從cache中查找old_method相當簡單高效俯画。從methodLists中查找old_method的真正過程分為如下兩步:
? ?1. 通過SEL在cache中查找old_method,若找到了直接返回司草,若未找到執(zhí)行2
? ?2. 在methodLists中查找old_method艰垂,找到之后先將old_method插入cache中以方便下次查找,再返回old_method 由此可知埋虹,在第一次調(diào)用某個函數(shù)時猜憎,會比較慢,因為cache中沒有這個函數(shù)吨岭,第二次調(diào)用時就會非忱冢快了
查找方式,先從cache 中尋找辣辫,如果沒有旦事,去改對象索引出來的方法列表,如果依然沒有找到急灭,繼續(xù)向上層類尋找姐浮,如果仍然沒有找到,并且實現(xiàn)了動態(tài)方法決議機制葬馋,就進行決議卖鲤,如果沒有實現(xiàn)動態(tài)決議機制或則決議肾扰,如果無論在哪一層的都會存在cache 中,方便下次調(diào)用
該博客是瀏覽大神博客之后根據(jù)自己的理解蛋逾,并親自試驗完成的集晚,如有雷同,純屬借鑒区匣。