分類做了哪些事情?
- 聲明私有方法
- 分解體積龐大的類文件
- 把Framework的私有方法公開化
- 可以添加實例方法, 類方法, 協(xié)議, 屬性(只添加了get/set方法, 沒有添加實例變量)
分類的特點
- 分類運行時決議,在運行時通過runTime把方法等添加到宿主類上. [擴展是編譯時決議, 這是擴展和分類的最大區(qū)別]
- 分類可以給系統(tǒng)類添加分類[不能給系統(tǒng)類添加擴展]
- 分類可以添加:
- 實例方法,
- 類方法,
- 協(xié)議,
- 屬性(只添加了get/set方法, 沒有添加實例變量)
- 可通過關聯(lián)對象添加實例變量
分類的結構體
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods; //實例方法
struct method_list_t *classMethods; //類方法
struct protocol_list_t *protocols; //協(xié)議
struct property_list_t *instanceProperties;//屬性
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
- 注意: 結構體中沒有關于實例變量的成員結構
分類加載調(diào)用棧
- 注意: image在這里表示鏡像, 程序鏡像或者內(nèi)存鏡像
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
/*
我們分析分類單重實例方法的添加邏輯
因此在這里我們假設 isMeta = NO
*/
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
//獲取cls中未完成整合的所有類
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
//將分類cats拼接到cls上
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;//如果無分類, 直接返回
if (PrintReplacedMethods) printReplacements(cls, cats);
/*
我們分析分類單重實例方法的添加邏輯
因此在這里我們假設 isMeta = NO
*/
bool isMeta = cls->isMetaClass();
/*二維數(shù)組
[[method_t, method_t...], [method_t],[method_t, method_t, method_t]]
*/
// 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));//協(xié)議列表
// Count backwards through cats to get newest categories first
int mcount = 0;//方法參數(shù)
int propcount = 0;//屬性參數(shù)
int protocount = 0;//協(xié)議參數(shù)
int i = cats->count;//分類總數(shù)
bool fromBundle = NO;
//倒敘遍歷, 后編譯先遍歷
while (i--) {
//獲取一個分類
auto& entry = cats->list[i];
//獲取該分類方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//最后編譯的分類,最先添加到數(shù)組中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//屬性列表添加規(guī)則,同方法列表添加規(guī)則
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;
}
}
//獲取宿主類當中rw數(shù)據(jù), 其中包含宿主類的方法列表信息
auto rw = cls->data();
//主要針對分類中有關內(nèi)存管理相關方法情況下的一些特殊處理
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
/*
rw:代表類
methods:類的方法列表
attachLists:將含有mcount個元素的mlists拼接到rw的methods上
[即添加到宿主類上, 這就是說為什么分類是運行時決議]
*/
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);
}
/*
addedLists:上一步傳過來二維數(shù)組
[[method_t, method_t...], [method_t],[method_t, method_t, method_t]]
---------A分類方法列表----- ----B---- ---------C----------
addedCount = 3
*/
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;//列表原有元素總數(shù)
uint32_t newCount = oldCount + addedCount;//拼接后元素總數(shù)
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));//根據(jù)總數(shù)重新分配內(nèi)存
array()->count = newCount;//重新設置元素個數(shù)
/*
內(nèi)存移動
[[A], [B],[C],[原來第一個],[原來第二個]]
*/
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
/*
內(nèi)存拷貝
[[A]-->[addedLists第一個元素]
[B],-->[addedLists第二個元素]
[C],-->[addedLists第三個元素]
[原來第一個元素],
[原來第二個元素]]
*/
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
1. 分類方法會"覆蓋"宿主類方法原因(其實分類方法和原宿主類方法都存在)
答: 通過memove和memcopy所做操作, 宿主類的方法任然存在, 只不過分類方法在宿主類方法之前, 在消息查找過程中, 根據(jù)選擇器(selector)來查找, 一旦查到就返回, 由于分類方法在前, 故分類方法會優(yōu)先實現(xiàn), 從而會覆蓋宿主類方法
2. 問:多個分類都有一個同名方法, 哪個生效?
答:后編譯的生效
總結: