Category是Objective-C 2.0以後添加的語言特性,Category的主要做用是爲已經存在的類添加方法,通常稱爲分類,文件名格式是"NSObject+A.h"。html
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;
struct property_list_t *_classProperties;
}
複製代碼
從結構能看出分類能夠擴展實例方法列表、類方法列表、協議列表,也支持擴展屬性,但不支持擴展成員變量(以後會說)。c++
通常使用的場景有擴展示有類方法、代碼分區、添加私有方法(不對外暴露category.h)、模擬多繼承(使用關聯對象的方式添加屬性實現)數組
Extension通常被稱爲類擴展、匿名分類,用於定義私有屬性和方法,不可被繼承。只能依附自定義類寫於.m中,定義通常爲:oop
@interface ViewController ()
@property (nonatomic, strong) NSObject *obj;
@end
複製代碼
類擴展支持寫在多個.h文件,但都必須在.m文件中引用,且不能有本身的實現。ui
類擴展不少時候會與分類搞混,我在文後問答環節詳細整理了他們的區別。atom
struct objc_class : objc_object {
Class superclass;
class_data_bits_t bits;
class_rw_t *data() {
return bits.data();
}
...
}
struct class_rw_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
...
}
struct class_ro_t {
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; //只有ro纔有實例變量表
property_list_t *baseProperties;
...
};
複製代碼
先簡單瞭解一下Class對象的結構,每一個objc_class
都包含有class_data_bits_t
數據位,其中儲存了class_rw_t
的指針地址和一些其餘標記。class_rw_t
中包含有屬性方法協議列表,以及class_ro_t
指針地址。而在class_ro_t
結構中,儲存的是編譯器決定的屬性方法協議。spa
在編譯期類的結構中的class_data_bits_t
指向的是一個 class_ro_t
指針。ssr
在運行時調用realizeClass
方法,初始化一個class_rw_t
結構體,設置ro值爲原數據中的class_ro_t
後設爲數據位中的指向,最後調用methodizeClass
方法加載。指針
static void methodizeClass(Class cls)
{
auto rw = cls->data();
auto ro = rw->ro;
//從ro中加載方法表
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
//加載屬性
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
//加載協議
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
//基類添加初始化方法
if (cls->isRootMetaclass()) {
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
//加載分類
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
if (cats) free(cats);
}
複製代碼
能夠看到,在methodizeClass
中加載了原先類在編譯期決定的方法屬性和協議,而後獲取了未鏈接的分類表,將列表中的擴展方法添加到運行期類中。code
若是不一樣的分類實現了相同名字的方法,那麼調用時會使用最後加入的實現,這是爲何呢?
dyld連接並初始化二進制文件後,交由ImageLoader
讀取,接着通知runtime
處理,runtime
調用map_images
解析,而後執行_read_images
分析文件中包含的類和分類。
//加載分類
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) {
//分類指定的類還沒加載,多是連接庫順序的問題
catlist[i] = nil;
continue;
}
//添加分類到類的分類表中,乘機重載入
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
//添加分類到元類中
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
複製代碼
若是有新增的分類,就分別添加到原類和meta類,並經過remethodizeClass
更新,具體就是調用attachCategories
方法把分類中全部的方法都添加到指定類中。
static void attachCategories(Class cls, category_list *cats, bool flush_caches) {
if (!cats) return;
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));
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();
//加載列表到rw中
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);
}
複製代碼
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;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
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]));
}
}
複製代碼
能夠看到最後調用了rw->methods.attachLists(mlists, mcount);
把新增分類中的方法列表添加到實際運行時查詢的方法列表頭部。
在進行方法調用時會從頭部查詢,一旦查到後就返回結果,所以後編譯的文件中的方法會被優先調用。
同時以前添加的方法實現也保存了,能夠經過獲取同名方法的方式查找原類的實現。
屬性(Property)包含了成員變量(Ivar)和Setter&Getter。
能夠在分類中定義屬性,但因爲分類是在運行時添加分類屬性到類的屬性列表中,因此並無建立對應的成員變量和方法實現。
若是咱們想讓分類實現添加新的屬性,通常都經過關聯對象的方式。
// 聲明文件
@interface TestObject (Category)
@property (nonatomic, strong) NSObject *object;
@end
// 實現文件
static void *const kAssociatedObjectKey = (void *)&kAssociatedObjectKey;
@implementation TestObject (Category)
- (NSObject *)object {
return objc_getAssociatedObject(self, kAssociatedObjectKey);
}
- (void)setObject:(NSObject *)object {
objc_setAssociatedObject(self, kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
複製代碼
這種方式能夠實現存取對象,可是不能獲取_object
變量。
1.分類多用於擴展方法實現,類擴展多用於申明私有變量和方法。
2.類擴展做用在編譯期,直接和原類在一塊兒,而分類做用在運行時,加載類的時候動態添加到原類中。
3.類擴展能夠定義屬性,分類中定義的屬性只會申明setter/getter,並無相關實現和變量。
1.分類只能給現有的類加方法或協議,不能添加實例變量(ivar)。
2.分類添加的方法若是與現有的重名,會覆蓋原有方法的實現。若是多個分類方法都重名,則根據編譯順序執行最後一個。
分類結構體包含了分類名,綁定的類,實例與類方法列表,實例與類方法屬性以及協議表。