Category的底層架構(gòu)
定義在objc-runtime-new.h中
分類相關(guān)信息合并到類或者元類源碼圖
里面的cats 指針數(shù)組存放的是多個(gè)分類方法的二維數(shù)組磷醋,例如[[personCategory1_(Test1)_run,personCategory1_(Test1)_eat],[personCategory2_(Test2)_run,personCategory2_(Test2)_eat]]等類似情況
可以看到,將分類的方法放到最前面抚官,將原來的類對(duì)象的方法或者元類方法放到最后面雁刷,這個(gè)時(shí)候如果調(diào)用了同名方法,分類的必定先于本身的類的相關(guān)方法執(zhí)行,但是如果兩個(gè)分類中都擁有同樣的方法弟胀,那么會(huì)是哪個(gè)方法先執(zhí)行呢,這個(gè)和編譯分類的先后順序有關(guān)的喊式,例如如果分類1先編譯孵户,分類2后編譯,然后兩個(gè)分類含有同樣的方法run(),那么從合并源碼圖可以看出岔留,這個(gè)賦值分類的方法是倒序遍歷夏哭,所以后編譯的放到最前面,所以后編譯的會(huì)先執(zhí)行献联,也就是會(huì)執(zhí)行分類2的run方法方庭;另外從源碼中可以看到使用了memmove,memcpy,為什么需要使用move方法呢酱固,直接copy過去效率不是更高么械念?使用move是為了確定數(shù)據(jù)的正確性,保證沒有被覆蓋运悲,后者使用cp是因?yàn)閿?shù)據(jù)是從別的地方拷貝過來龄减,不存在覆蓋一說,如果還是對(duì)這兩個(gè)方法有點(diǎn)疑惑班眯,可以參考這個(gè)鏈接
+load方法調(diào)用時(shí)機(jī)和調(diào)用方式
1.每個(gè)類希停,分類的load方法在程序運(yùn)行過程中只調(diào)用一次
2.先調(diào)用類的+load方法烁巫,按照編譯先后順序調(diào)用,先編譯先調(diào)用宠能,另外調(diào)用子類的+load之前會(huì)先調(diào)用父類的+load方法
3.再調(diào)用分類的load方法亚隙,按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
下面先上傳一張測試效果圖违崇,然后再從源碼分析
也許有人會(huì)疑惑阿弃,上面不是說是倒序遍歷,先編譯的分類羞延,方法后調(diào)用么渣淳,這里load是特殊的,因?yàn)閘oad調(diào)用和其他方法不一樣伴箩,從源碼分析:objc-os.mm->_objc_init->load_images->prepare_load_methods->schedule_class_load->add_class_to_loadable_list->add_category_loads->(*load_method)(cls,SEL_load)
從上面的結(jié)果除開可以看到load方法的調(diào)用機(jī)制入愧,還可以知道load方法是根據(jù)方法地址直接調(diào)用,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用
initialize方法的調(diào)用機(jī)制
+initialize 方法會(huì)在類第一次接受到消息的時(shí)候調(diào)用嗤谚,測試圖:
從上面可以得出結(jié)果是先調(diào)用父類的initialize,再調(diào)用子類的+initialize,并且每個(gè)類的初始化方法只會(huì)調(diào)用一次棺蛛,并且分類中如果實(shí)現(xiàn)了initialize方法,會(huì)覆蓋類本身的initialize巩步,這也證明了initialize是根據(jù)消息發(fā)送機(jī)制調(diào)用的鞠值,找到了就不會(huì)往下傳遞了 ,源碼尋找圖:
源碼解讀過程objc_msg_arm64.s->objc_msgSend? objc-runtime-new.mm->class_getInstanceMethod->lookUpImpOrNil->lookUpImpOrForward->_class_initialize->callInitialize->objc_msgSend(cls,SEL_initialize)
總結(jié)補(bǔ)充
1.Category實(shí)現(xiàn)原理
Category編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲(chǔ)著分類的對(duì)象方法渗钉,類方法彤恶,屬性,協(xié)議信息鳄橘,在程序運(yùn)行的時(shí)候声离,runtime會(huì)將Category的數(shù)據(jù)合并到類信息中(類對(duì)象,元類對(duì)象中)
2.Category和Class Extension的卻別是瘫怜?
Class Extension在編譯的時(shí)候术徊,它的數(shù)據(jù)就已經(jīng)包含在類信息里面了,而Category是在運(yùn)行時(shí)鲸湃,才會(huì)將數(shù)據(jù)合并到類信息中赠涮,所以這個(gè)時(shí)候類結(jié)構(gòu)已經(jīng)確定了,因此不能直接添加成員屬性暗挑,但是可以通過associate的全局功能進(jìn)行聲明和定義
3.Category的加載處理過程是怎樣的
通過runtime加載某個(gè)類的所有Category數(shù)據(jù)笋除,然后把所有的Category的方法,屬性炸裆,協(xié)議數(shù)據(jù)垃它,合并到一個(gè)大數(shù)組中,后面參與編譯的Category數(shù)據(jù),會(huì)在數(shù)組的前面国拇,繼續(xù)將合并后的分類數(shù)據(jù)(方法洛史,屬性,協(xié)議)插入到原來的數(shù)據(jù)的前面酱吝,這個(gè)結(jié)論可以通過源碼來解讀也殖,因?yàn)檫@些都是在運(yùn)行時(shí)合并的,所以閱讀源碼的書序务热,應(yīng)該從運(yùn)行時(shí)的過程開始理解objc-os.mm->objc_init->map_images->map_images_nolock------->objc-runtime-new.mm-->read-images->remethodizeClass->attachCategories->attachLists->realloc,memmove,memcpy
4.initialize 和 load的區(qū)別忆嗜?
+initialize是通過objc_msgSend進(jìn)行調(diào)用的,load是通過地址調(diào)用的陕习,子類沒有實(shí)現(xiàn)+initialize會(huì)調(diào)用父類的+initialize霎褐,所以父類的initialize可能會(huì)調(diào)用多次址愿,那是因?yàn)閺脑创a中可以得到该镣,如果父類沒有初始化,會(huì)調(diào)用父類响谓,然后再接著處理自己损合,因?yàn)樽约簺]有沒有這個(gè)重寫+initialze方法,所以根據(jù)runtime往父類中查找娘纷,最后找到父類或舞,另外initialize和load調(diào)用機(jī)制不一樣梁剔,如果分類了實(shí)現(xiàn)+initialize,就會(huì)覆蓋類本身的initialize的調(diào)用,load不會(huì)彤蔽,而且load方法的調(diào)用機(jī)制和原理已經(jīng)說得很清楚了
可以添加微信一起交流學(xué)習(xí):fslskz