本文是Objective-C系列的第10篇,主要講述了category的實現原理和相關特性。git
category是Objective-C 2.0以後添加的語言特性,分類、類別其實都是指的category。category的主要做用是爲已經存在的類添加方法。github
能夠把類的實現分開在幾個不一樣的文件裏面。這樣作有幾個顯而易見的好處。數組
不過除了apple推薦的使用場景,還衍生出了category的其餘幾個使用場景:緩存
extension被開發者稱之爲擴展、延展、匿名分類。extension看起來很像一個匿名的category,可是extension和category幾乎徹底是兩個東西。bash
和category不一樣的是extension不但能夠聲明方法,還能夠聲明屬性、成員變量。extension通常用於聲明私有方法,私有屬性,私有成員變量。app
使用 extension 必須有原有類的源碼。extension 聲明的方法、屬性和成員變量必須在類的主 @implementation
區間內實現,能夠避免使用有名稱的 category 帶來的多個沒必要要的 implementation 段。iphone
extension 很常見的用法,是用來給類添加私有的變量和方法,用於在類的內部使用。例如在 interface 中定義爲 readonly 類型的屬性,在實現中添加 extension,將其從新定義爲 readwrite,這樣咱們在類的內部就能夠直接修改它的值,然而外部依然不能調用 setter 方法來修改。佈局
category和extension的區別來看,post
首先,咱們編寫下面這些類,源碼在:01-principleui
重編譯爲C++代碼:
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc BFPerson+Work.m
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc BFPerson+Study.m
複製代碼
以Study分類爲例,看看C++中的結構體:
其中對應初始化中的部分結構體以下:
咱們看到在編譯期中針對分類中的,實例方法、類方法以及屬性(假若有協議一樣會生成,示例代碼中未繼承協議)都生成了對應的結構體。
最後造成的結構體就是上圖中的_category_t
:
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; //屬性列表
};
複製代碼
源碼在:01-principle
runtime會加載某個類的全部category數據;
將全部category的方法(對象方法、類方法)、屬性、協議數據,合併到一個大數組中
將合併後的分類數據(方法、屬性、協議),插入到類原來數據的前面;
下面咱們從objc源碼開始追蹤這些結論的體現。
//from objc-runtime-new.h
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;
// 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);
};
複製代碼
下面源碼,咱們以分類中的對象方法爲例。其類方法、協議或屬性,和對象方法處理流程相似,遵循一樣的規律。
源碼執行流程以下:
下面是分類方法合併到原類中的流程圖:
咱們讀取最核心重佈局對象方法的環節:
項目編譯中,Compile Sources會決定分類編譯順序,下圖中Study分類在前,Work分類在後。
cats
獲得列表,Study在前,Work在後。
cats = [
category_t (BFPerson+Study),
category_t (BFPerson+Work)
]
cats
方法合併到mlists
將分類cats
倒序抽取出每一個分類的方法,放入二維數組mlists
中,此時Work對象方法在前:
mlists = [ [method_t work, method_t test], [method_t study, method_t test], ]
mlists
插入rw
將mlists
方法插入rw
對象方法列表method_array_t
中
[method_t test]
移動到method_array_t
最後;mlists
方法拷貝到method_array_t
頭部。最後,咱們得到的rw
中method_array_t
爲:
method_array_t = [
[method_t study, method_t test], <-->BFPerson+Work
[method_t work, method_t test], <-->BFPerson+Study
[ method_t test], <-->BFPerson
]
下面是抽取的部分源碼:
// cls - BFPerson
// *cats = [category_t (BFPerson+Study), category_t (BFPerson+Work) ]
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
// 分配內存:針對未進行重佈局的分類列表
/*mlists 方法二維數組*/
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
int i = cats->count;
while (i--) {
// i-- 在category list最後面的,先添加到mlists
// 取出分類列表中的分類
// 最後參與編譯的,先取出category_t BFPerson+Work
// 編譯順序:在編譯log中查看,在Compile Sources中更改
auto& entry = cats->list[i];
// 取出分類的對象方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
// 將對應分類的方法列表添加到mlists
/* 以 *cats = [category_t (BFPerson+Study), category_t (BFPerson+Work)] 執行完while 循環後,mlists爲 [ [method_t work, method_t test], [method_t study, method_t test], ] */
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 將從新佈局的category list添加到 rw中
// 將全部分類的對象方法,附加到類對象的方法列表中
rw->methods.attachLists(mlists, mcount);
free(mlists);
// 刷新方法緩存列表
if (flush_caches && mcount > 0) flushCaches(cls);
}
複製代碼
而後開始從新編排rw
中的方法列表:
/** @param addedLists 分類方法列表————二維數組 [ [method_t work, method_t test], [method_t study, method_t test] ] @param addedCount 分類列表長度————即分類的數目 */
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;
//從新分配實例對象數組列表內存
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// array()->lists 原來的方法列表
// *memmove(void *__dst, const void *__src, size_t __len);
// 將原方法列表array()->lists開始,移動oldCount長度,移動到array()->lists + addedCount(分類個數)
// 此處,假如原單位佔用1個,分類此處有兩個(Study、Work)
// 【1】【】【】-> 【】【】【1】
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// *memcpy(void *__dst, const void *__src, size_t __n);
// 將addedLists開始的addedCount長度,拷貝到array()->lists位置
// 【1】【】【】-> 【】【】【1】-> 【2-Work】【3-Study】【1】
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
複製代碼