Category中是原生開發中經常用到的一個技術實現,利用這一技術可以在不知道原始類實現的情況的情況下未類添加屬性和方法實現,可以很好對類功能進行擴展.本文主要討論兩個問題:
- Category中的實現如何附加到對應類中;
- Category中被"覆蓋的方法",如何能調用到原始實現.
Category加載
OC最重要的特性就是運行時,而Category之所以與衆不同就在於其加載也是在運行時.在程序加載時內核會通過dyld將machO文件加載到內存進行解析,然後通過_objc_init來進行應用的初始化.
_objc_init
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();
//調用C++靜態構造函數
static_init();
//這個函數的實現是空,但是根據名稱可以判斷應該是初始化一些鎖操作
lock_init();
//初始化異常捕獲:當執行出現異常時調用對應的異常函數
exception_init();
//註冊函數通知(鏡像的映射,加載,解除映射),在這裏主要看map_images的實現
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
_dyld_objc_notify_register
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
...
...
//在進行了一些列的解析操作開始讀取鏡像
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
_read_images
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
...
// Discover categories.
for (EACH_HEADER) {
//獲取到當前(image)hi中的所有category列表
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
//遍歷category列表中的所有category
category_t *cat = catlist[i];
//返回cls的實時類指針,如果由於弱鏈接而忽略cls,則返回nil。
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.
/*
處理當前分類:
1. 將當前分類與其對應的類進行註冊;
2. 重建類中的方法列表(rw->methodList)
*/
bool classExists = NO;
//處理當前分類中的對象實現(實例方法,實例屬性以及協議)
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
printf("%s已經初始化\n", class_getName(cls));
remethodizeClass(cls);
classExists = YES;
} else {
printf("%s尚未初始化\n", class_getName(cls));
}
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))
{
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);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
...
...
}
addUnattachedCategoryForClass
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertLocked();
//獲取全局靜態變量cats:該散列表主要維護class與categoty的對應關係,每個class對應了一個list
NXMapTable *cats = unattachedCategories();
category_list *list;
//獲取當前類對應的category列表
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
//如果當前類對應的category列表不存在就初始化一個
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
//如果當前類對應的category列表存在就重新擴容
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
//將新的category添加在列表的後端
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
//插入當前新的list
NXMapInsert(cats, cls, list);
}
remethodizeClass
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
//判斷當前類是不是元類
isMeta = cls->isMetaClass();
// 獲取需要附加到對應類中的Category列表
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
//開始附加Category列表到當前類中
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
//從全局散列表中獲取到當前類對應的Category列表,並將該Category列表從全局散列表中刪除
static category_list *
unattachedCategoriesForClass(Class cls, bool realizing)
{
runtimeLock.assertLocked();
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();
//重新排列Categoty列表中的Category順序
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();
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()) {
// 當前類的列表中的數量已經大於1: 多個列表->多個列表
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: 0個列表 -> 一個列表
list = addedLists[0];
}
else {
// 當前類的列表中的數量等於1: 1個列表 -> 多個列表
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]));
}
}
根據分析,可以看出:
- category中的方法會出現在原始類方法列表的前邊;
- 多個category中方法,最後編譯的方法會出現在在它之前編譯的方法之前;
- category中的同名方法並沒有覆蓋原來的方法實現,而是出現在方法數組的前邊.換句話來說,category中的同名方法只是優先級別原來的方法高,在方法調用時優先被調用.
Category中出現同名方法時,如何調用原來的方法實現?
由於Category中的同名方法並沒有覆蓋原來的方法,所以是依然可以查找到原始方法的實現的;由於Category中的同名方法比原來的實現優先級高,所以在正常情況下原始實現是不會被調用到的.如果在Category中重寫本類方法,那麼應該如何調用到原來的方法實現呢?這裏只對僅有一個分類的情況做討論:
@interface Man : NSObject
- (void)hello;
@end
@implementation Man
- (void)hello {
NSLog(@"Man[%s]", __FUNCTION__);
}
@end
@interface Student : Man
- (void)sayHello;
@end
@implementation Student
- (void)sayHello {
NSLog(@"Student[%s]", __FUNCTION__);
}
@end
- Category和其對應的類本類同時實現了同名方法:
-
@interface Student (Category) @end @implementation Student (Category) -(void)sayHello { NSLog(@"Student (Category)(%s)", __FUNCTION__); } @end
那麼這時可以遍歷類的方法的列表,查找到位於最後一個位置的方法就是原始的方法實現:
-
Student *stu = [[Student alloc] init]; //[stu sayHello]; 由於Category同名方法會在原始方法之前,所以直接調用就會調用到Category中的實現 unsigned int outCount = 0; Method *methods = class_copyMethodList([Student class], &outCount); for(unsigned int index = outCount; index ; index--) { Method method = methods[index]; if (strcmp(sel_getName(method_getName(method)), "sayHello") == 0 ) { void(*imp)(id, SEL) = (void(*)(id, SEL))method_getImplementation(method); imp(stu, @selector(sayHello)); break; } }
- Category和其對應的類本類中的某個父類同時實現了同名方法:
@interface Student (Category)
@end
@implementation Student (Category)
- (void)hello {
NSLog(@"Student (Category)(%s)", __FUNCTION__);
}
@end
這時在Category對應的本類中並沒有實現同名方法,所以在本類的方法列表中查找不到對應的方法,於是可以直接調用父類的實現.
Student *stu = [[Student alloc] init];
[stu hello]; //直接進行方法調用會調用到Category中的方法實現
//由於Category對應的本類(Student)中並沒有實現hello方法,所以可以使用父類中的方法實現
struct objc_super superObj = {
.receiver = stu,
.super_class = class_getSuperclass([Student class]),
};
((void(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superObj, @selector(hello));
綜合兩種情況,可以將代碼進行合併:
Student *stu = [[Student alloc] init];
unsigned int outCount = 0;
SEL sel = sel_registerName("sayHello");
typedef void(*MethodImplementation)(id, SEL);
MethodImplementation imp = NULL;
Method *methods = class_copyMethodList([Student class], &outCount);
unsigned int methodCount = 0;
for(unsigned int index = outCount; index ; index--) {
Method method = methods[index];
SEL methodSEL = method_getName(method);
if (methodSEL && methodSEL == sel) {
methodCount++;
if (!imp) {
imp = (MethodImplementation)method_getImplementation(method);
}
}
}
if (methodCount != 2) {
//這個判斷只適用於當前類(Student)僅有一個Category,且Category中包含同名方法(與本類或者本類父類存在同名方法)的判斷
imp = NULL;
}
if (imp) {
imp(stu, sel);
} else {
struct objc_super superObj = {
.receiver = stu,
.super_class = class_getSuperclass([Student class]),
};
((void(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superObj, sel);
}