事由:以前去面試,而後面試官問了我一些關於
runtime
的用法,我有說到Method Swizzling
。經過在category
的load
中去修改咱們調用的方法,來達到全局修改的目的。隨後面試官問到關於category
的實現,哇! 尷尬,我好像歷來沒有想過這個問題。如今有時間就給整理一下,水平有限,確定會有不少不足。但願你們多多指點!多謝 zzz面試
category
是Objective-C 2.0
以後添加的語言特性. 通常咱們使用它有如下兩種場景設計模式
組合
的設計模式把類的實現分開成多個category
在幾個不一樣的文件裏面
category
裏category
,達到業務分離關於問題(由於確實不知道該從什麼地方開始看起,因此強迫試的給本身定了幾個問題。讓本身去弄明白)數組
category
是什麼東西category
是怎樣加載的category
方法爲何能夠覆蓋宿主類的方法category
的屬性跟方法是怎麼添加到宿主類的category
爲何能夠添加屬性,方法,協議。缺不能添加成員變量category
是什麼東西objc
全部類和對象都是c
結構體,category
也同樣。咱們能夠經過clang
去看一下安全
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
複製代碼
_category_t
裏面有名字、宿主類的對象、實例方法列表、類方法列表、協議方法列表、屬列表性。能夠看到是沒有成員變量列表的,由於category
是依賴runtime
的,而在運行時對象的內存佈局已經肯定,若是添加實例變量就會破壞類的內部佈局,這對編譯型語言來講是災難性的。這就是爲何咱們沒有在_category_t
裏面找到成員變量列表和category
不能夠添加成員變量的緣由bash
category
是怎樣加載的上面咱們提到過category
是依賴runtime
的。那咱們來看一下runtime
的加載過程。下面用到的runtime
源碼都來自於點這! 我下載的是723
最新的版本app
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼
開始是一些初始化方法函數
map_images
方法表示將文件中的二進制文件映射到內存,category被添加到宿主類就發生在這個方法裏面。咱們來看一下這個方法實現佈局
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
// 加鎖操做 保證在映射到dyld過程調用ABI是安全的
rwlock_writer_t lock(runtimeLock);
//函數在加鎖後就轉向了 map_images_nolock 函數
return map_images_nolock(count, paths, mhdrs);
}
複製代碼
map_images_nolock
方法代碼太長,我就不粘過來了。它主要作的操做是在函數中,它檢查傳入的每一個image
,若是image
有須要的信息,就將它記錄在hList
中,並將hCount
加一,最終判斷hCount>0
來調用_read_images
讀取 image 中的數據 。學習
咱們再來看_read_images
,方法有點長。我給跟category
相關代碼截取出來了ui
for (EACH_HEADER) {
// 獲取 鏡像中的全部分類
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked). // Disavow any knowledge of this category. catlist[i] = nil; if (PrintConnecting) { _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " "missing weak-linked target class", cat->name, cat); } continue; } // Process this category. // First, register the category with its target class. // Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
// 從這開始,正式對category開始處理
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
//爲類添加未依附的分類,把Category和類關聯起來
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
//爲類添加未依附的分類,把Category和metaClass關聯起來。由於類方法是存在元類中的
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
複製代碼
哇! 終於 終於 到了最關鍵的方法了
首先。咱們調用remethodizeClass
來調用category
的幕後大佬----attachCategories
方法,從名字就能夠看出來它的做用,添加category
plz show me the code !!!
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
/*
#warning attachCategories
category的加載順序是經過編譯順序決定的
這樣倒序遍歷,保證先將數組內元素(category)的方法從後往前添加到新數組
這樣編譯在後面的category方法會在數組的前面
*/
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
複製代碼
獲得從新組合的數組在調用
attachLists
方法
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
//C 庫函數 void *realloc(void *ptr, size_t size) 嘗試從新調整以前調用 malloc 或 calloc 所分配的 ptr 所指向的內存塊的大小
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
/*
1.memmove
函數原型:void *memmove(void *dest, const void *source, size_t count)
返回值說明:返回指向dest的void *指針
參數說明:dest,source分別爲目標串和源串的首地址。count爲要移動的字符的個數
函數說明:memmove用於從source拷貝count個字符到dest,若是目標區域和源區域有重疊的話,memmove可以保證源串在被覆蓋以前將重疊區域的字節拷貝到目標區域中。
2.memcpy
函數原型:void *memcpy(void *dest, const void *source, size_t count);
返回值說明:返回指向dest的void *指針
函數說明:memcpy功能和memmove相同,可是memcpy中dest和source中的區域不能重疊,不然會出現未知結果。
3.二者區別
函數memcpy() 從source 指向的區域向dest指向的區域複製count個字符,若是兩數組重疊,不定義該函數的行爲。
而memmove(),若是兩函數重疊,賦值仍正確進行。
memcpy函數假設要複製的內存區域不存在重疊,若是你能確保你進行復制操做的的內存區域沒有任何重疊,能夠直接用memcpy;
若是你不能保證是否有重疊,爲了確保複製的正確性,你必須用memmove。
*/
/*
這樣就完成將category方法列表裏面的方法 加到 class的方法列表裏面並且是前面。等到咱們再去調用class的方法時候,咱們經過去遍歷class的方法列表去查到SEL,找到就會調用相應方法。因爲category的方法在前面---致使因此會覆蓋宿主類原本的方法(這就是爲何category方法的優先級高於宿主類方法)
屬性和協議同理!!!!!!!!!!!
*/
//至關於給array()->lists 從新放在起始地址 = array()->lists的起始地址 + addedCount
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//至關於給addedLists 這個category新數組加到array()->的起始地址
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
複製代碼
以上應該能夠回答,以前提的全部問題。若有疑問,能夠聯繫我。
筆者是一個剛入門iOSer
此次關於category
的管中窺豹,必定有不少的不足,但願你們不吝賜教!
有任何問題能夠留言,或者直接聯繫QQ:346658618
但願能夠相互學習,一塊兒進步!