2019-10-15數組
本文介紹分類(Category)的實現以及分類的加載過程。分類是對 Objective-C 類的一種擴展方式。說到分類不可不提擴展(Extension)。擴展一般被視爲匿名的分類,可是二者實現的區別仍是很大的:bash
@implementation
塊中,分類是對接口以及實現的擴展,分類的實如今@implementation(CategoryName)
塊中;category_t
結構體與之對應,而擴展則沒有;注意:分類是裝飾器模式。用分類擴展的好處是:對父類的擴展能夠直接做用於其全部的衍生類。數據結構
分類的數據結構是category_t
結構體。包含了分類名稱name
,分類所擴展的類cls
,分類實現的實例方法列表instanceMethods
,分類實現的類方法列表classMethods
,分類遵循的協議列表protocols
,分類定義的屬性列表instanceProperties
。ui
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
複製代碼
類並不包含像分類列表這樣的數據結構,category_t
結構體只是爲了在編譯階段記錄開發者定義的分類,並將其保存到特定的容器中。可是程序自己則須要保存分類列表,由於加載程序時,須要按照容器內記錄的分類信息依次加載分類。保存應用定義的全部分類的容器是category_list
,也是locstamped_category_list_t
的別名。locstamped_category_list_t
是順序表容器,元素爲locstamped_category_t
結構體。locstamped_category_t
結構體包含指向category_t
結構體的cat
成員。spa
// 分類列表category_list是locstamped_category_list_t的別名
typedef locstamped_category_list_t category_list;
// 分類列表中的元素的類型,包含指向分類的指針
struct locstamped_category_t {
category_t *cat;
struct header_info *hi; // 先忽略
};
// locstamped_category_list_t 數組容器是實現了分類列表
struct locstamped_category_list_t {
uint32_t count;
#if __LP64__
uint32_t reserved;
#endif
locstamped_category_t list[0];
};
複製代碼
分類的加載是在應用的加載階段,當應用完成類的基本信息(編譯時決議的信息)加載後,須要將類的全部分類中的定義屬性列表、方法列表等元素也添加到類的class_rw_t
中。ssr
分類加載的核心代碼在_read_images(...)
邏輯中。其中的關鍵環節包括:一、調用addUnattachedCategoryForClass(...)
將分類添加到類的 未處理分類列表;二、調用remethodizeClass(...)
根據分類重構類的方法列表,兩個處理成對存在。前者收集全部未加載的分類列表,後者將未加載的分類列表中的方法列表、屬性列表等信息加載到類中。指針
void _read_images(header_info **hList, uint32_t hCount)
{
#define EACH_HEADER \
hIndex = 0; \
crashlog_header_name(nil) && hIndex < hCount && (hi = hList[hIndex]) && crashlog_header_name(hi); \
hIndex++
...
// 加載分類
for (EACH_HEADER) {
// 獲取分類列表,分類列表的順序是後編譯的分類在前(由attachCategories中分類列表的遍歷順序,
// 結合attachList的行爲特徵 以及 類的不一樣分類定義的同名方法的調用優先級,共同推斷出)
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// 類爲空則丟棄該分類的全部信息
catlist[i] = nil;
continue;
}
// 首先將分類添加到類的未處理分類
// 而後處理逐個類的未處理分類,重構類的屬性列表、方法列表
// 類的處理
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
// 必須保證 class realizing 已完成,由於`class_ro_t`信息固定後
// 才能開始配置`class_rw_t`中的信息
remethodizeClass(cls);
classExists = YES;
}
}
// 元類的處理
if (cat->classMethods || cat->protocols
/* || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
...
}
複製代碼
還沒有將方法列表、屬性列表等信息添加到所擴展類的class_rw_t
中 的分類,統一保存在一個靜態的NXMapTable
類的哈希表中。該哈希表以Class
做爲關鍵字,category_list
爲值。經過unattachedCategories()
獲取該哈希表。(category_list *)NXMapGet(unattachedCategories(), cls)
表示獲取類cls
的未處理分類列表,須要對cls
添加未處理分類時,將其添加到類的未處理分類列表的結尾。code
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// 不可用cat->cls替代,由於cls多是cat->cls->isa(操做類方法時)
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
// 將分類添加到list的末尾
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
static NXMapTable *unattachedCategories(void)
{
runtimeLock.assertWriting();
static NXMapTable *category_map = nil;
if (category_map) return category_map;
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
return category_map;
}
複製代碼
remethodizeClass
用於處理 2.1 中生成的unattachedCategories
哈希表中的全部分類 ,將分類擴展的方法、屬性、協議,逐一添加到類的方法列表、屬性列表、協議列表中。類的方法列表二維數組容器中後編譯的分類的method_list_t
在前,先編譯的分類的method_list_t
在後。在方法列表中查詢方法時是以從開頭到結尾的順序遍歷找到第一個匹配的方法返回。所以,響應方法時,後編譯的分類的方法的優先級,高於先編譯的分類的同名方法。對象
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
// 將類的未處理分類列表從哈希表中推出
static category_list *
unattachedCategoriesForClass(Class cls, bool realizing)
{
runtimeLock.assertWriting();
return (category_list *)NXMapRemove(unattachedCategories(), cls);
}
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
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));
// 注意分類列表的遍歷是從後向前的,所以方法列表二維數組容器中,元素對應的分類的順序
// 和cats是一致的。
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);
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);
}
複製代碼
在應用載入階段,將代碼中定義的分類封裝成category_t
結構體保存,將全部分類信息收集到unattachedCategories
哈希表中,用於記錄 還沒有將方法列表、屬性列表等信息添加到所擴展類的class_rw_t
中 的分類。哈希表以分類所擴展的類(Class)爲關鍵字,擴展了該類的全部分類組成的分類列表爲值,分類列表的數據結構是category_list
結構體;接口
完成收集unattachedCategories
哈希表後,將unattachedCategories
中全部分類的方法列表、屬性列表、協議列表添加到 所擴展類的class_rw_t
中;
後編譯的分類的方法的優先級,高於先編譯的分類的同名方法。即在類的方法列表二維數組容器中,後編譯的分類的method_list_t
在前,先編譯的分類的method_list_t
在後,類的基礎方法列表在最末尾;
下一篇介紹 runtime 實現面向對象的一些具體實現細節;