
簡介
人類在進步,社會在發(fā)展,隨著時間變化我們會遇到不同的問題,由此也誕生了對應(yīng)的解決方法。我們編寫的類也是如此,在隨著業(yè)務(wù)的發(fā)展,原定類的方法也就不足以處理新的業(yè)務(wù),因此我們開始想方設(shè)法去擴展類以處理新業(yè)務(wù)。在 Objective-C 2.0 提供的 category 特性,此特性可為已有類添加新的方法。除了為已有類添加新方法外,我們可以運用分類的特性去抽離出復(fù)雜類的業(yè)務(wù),降低類的復(fù)雜度。
與 category 相似的 extension
extension(類擴展) 從代碼看起來和 category 十分相像,代碼如下:
// extension
@interface Father ()
@property (nonatomic,strong) NSString *name;
- (void)say;
@end
// category
@interface Father (skill)
- (void)fastTalk;
@end
雖然代碼看上去只有括號中有名稱的不同,但其實它們是完全不同的,它們的不同之處如下:
- 在編譯上,extension 在編譯期時被確定,而 category 是在運行期被確定的。
- extension 一般是用來隱藏類的私有信息,而且你必須在類的源碼文件處才能添加。而 category 不同,只要類存在,就能為其添加 category。
- category 不能添加實例變量,這是它與 extension 最大的區(qū)別。(category 能通過類關(guān)聯(lián)添加屬性,這在后面提到)
category 實現(xiàn)和加載
category 的源碼定義如下
typedef struct objc_category *Category;
// objc 2.0
struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
}
// objc_category 的實現(xià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;
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);
};
從定義中我們可以看到:
- name:分類名稱
- cls:分類所屬類
- instance_methods:用于存儲實例方法
- class_methods:用于存儲類方法
- protocols:用于存儲協(xié)議方法
- instanceProperties:用于存儲添加的屬性,不會生成實例變量,因此也不會生成 setter 和 getter 方法。
- _classProperties:用于存儲添加的類的屬性,該屬性不會生成 setter 和 getter 方法。
為什么存在 instanceProperties 和 _classProperties
_classProperties 類的屬性,對于我們來說可能很陌生,在開發(fā)中很少使用,它的定義和我們常定義的屬性差不多,只不過 setter 和 getter方法為 “+ 方法”(類方法)。
// instance property
@property (nonatomic,strong) NSString *name;
- (NSString *)name;
- (void)setName:(NSString *)name
// class property
@property (class,nonatomic,strong) NSString *name;
+ (NSString *)name;
+ (void)setName:(NSString *)name
在前面說了:category 是不能添加實例變量的,我們可能就會疑惑了,為什么會有 instanceProperties 和 _classProperties,這我們就得明白成員變量、實例變量、屬性的聯(lián)系了。
@interface Father : NSObject {
int age;
NSData *data;
}
@property (nonatomic,strong) NSString *name;
@end
成員變量
在 @interface Father : NSObject {} 中聲明的變量都是成員變量,編譯器不會自動為成員變量聲明 setter 和 getter 方法,因此我們無法通過 .語法糖訪問成員變量,可通過 self -> 變量名或直接使用變量名,如:
age = 23;
self -> age = 24;
實例變量
實例變量又是什么呢,從本質(zhì)上,實例變量就是成員變量,實例變量從名字上就能體現(xiàn)出其是類定義的變量。如 data 就是由 NSData 類定義的實例變量,而 age 是基本數(shù)據(jù)類型 int 變量,因此 age 不是實例變量。所以我們大概可以總結(jié)出:成員變量 = 實例變量 + 基本數(shù)據(jù)類型變量。
屬性
成員變量用于類內(nèi)部,我們無法從類外部訪問成員變量,因此誕生了“屬性”,屬性可以允許在內(nèi)外部方法,在外部使用 類實例.屬性 訪問,在類內(nèi)部使用 self.屬性名或者 _變量名訪問,但前提是為屬性生成了實例變量以及實例變量的 setter 和 getter 方法。
在 iOS 5 前使用 @property 聲明屬性并聲明屬性的 setter 和 getter 方法,需在 .m 實現(xiàn)文件中使用 “@sythesize 屬性名 = _屬性名” 生成與屬性對應(yīng)的實例變量以及實現(xiàn) setter 和 getter 方法,在 iOS 5 后使用 @property聲明屬性,編譯器會自動為屬性生成一個名稱為 _屬性名的實例變量并實現(xiàn) setter 和 getter 方法。
@sythesize
@sythesize 除了為屬性生成 setter 和 getter 方法外還能指定屬性對應(yīng)的實例變量的名稱,如 @sythesize name,那么 name 屬性的實例變量名不再是默認生成的 _name 而是 name,如果 @sythesize name = _name 的話,實例變量名依然為 _name。
在 category 中添加屬性
在 category 中添加屬性后能編譯成功,但一旦使用了屬性,程序便會崩潰,原因為:未找到屬性的 setter 或 getter 方法的實現(xiàn),于是我們開始為屬性手動實現(xiàn) setter 或 getter 方法,我們會發(fā)現(xiàn)無法訪問到屬性。通過 runtime 的 class_copyPropertyList() 獲取類的所有屬性 以及 class_copyIvarList() 獲取類的變量,我們可以發(fā)現(xiàn),在屬性列表中存在該屬性,而在變量列表中卻沒有對應(yīng)的實例變量,所以我們可知編譯器未給屬性生成對應(yīng)的實例變量,因此手動實現(xiàn) setter 和 getter 方法的想法破滅了。當我們試圖在 category 創(chuàng)建成員變量時,編譯器會報 Instance variables may not be placed in categories 錯誤。
正所謂上有政策下有對策,我們可以創(chuàng)建一個全局變量來保存屬性值,就如這樣:
@interface NSObject (test)
@property (nonatomic,strong) NSString *name;
@end
NSString *_name;
@implementation NSObject (test)
- (NSString *)name {
return _name;
}
- (void)setName:(NSString *)name
{
_name = name;
}
@end
或者我們可以通過 runtime的關(guān)聯(lián)對象 來實現(xiàn)屬性的 setter 和 getter 方法
@interface NSObject (test)
@property (nonatomic,strong) NSString *name;
@end
#import <objc/runtime.h>
@implementation NSObject (test)
- (NSString *)name {
return objc_getAssociatedObject(self, @selector(name));
}
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
所以在 category 中無法添加實例變量,但是可以添加屬性,但是不能添加類屬性(后節(jié)分類的加載有提到)。
編譯器對 category 的處理
@interface NSObject (Category)
- (void)printDescription;
@end
@implementation NSObject (Category)
- (void)printDescription
{
NSLog(@"%@",self.description);
}
@end
使用 clang -rewrite-objc 文件名.m 命令對上面代碼轉(zhuǎn)換成源碼
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printDescription", "v16@0:8", (void *)_I_NSObject_Category_printDescription}}
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;
static struct _category_t _OBJC_$_CATEGORY_NSObject_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"NSObject",
0, // &OBJC_CLASS_$_NSObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_Category,
0,
0,
0,
};
static void OBJC_CATEGORY_SETUP_$_NSObject_$_Category(void ) {
_OBJC_$_CATEGORY_NSObject_$_Category.cls = &OBJC_CLASS_$_NSObject;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_NSObject_$_Category,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_NSObject_$_Category,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我們可以看到:
- 首先,編譯器生成了實例方法列表 OBJC
_Category
- 然后,用生成的實例方法列表初始化 OBJC
Category (NSObject+Category),在 OBJC_CATEGORY_SETUP
_Category 設(shè)置方法中將 OBJC
Category 的成員變量 cls 指向 OBJC_CLASS$_NSObject (NSObject 類)。
- 最后,將 NSObject+Category 分類的設(shè)置方法 OBJC_CATEGORY_SETUP_
Category 保存到 OBJC_CATEGORY_SETUP[] 中,將 NSObject+Category 分類 保存到 DATA段下的objc_catlist section里的 L_OBJC_LABEL_CATEGORY$ 中,在運行期時用于分類的加載。
category 加載
Objective-C 運行時入口方法:
// objc4-706 objc-os.mm
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();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_2_images, load_images, unmap_image);
}
// objc4-680 objc-os.mm
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();
static_init();
lock_init();
exception_init();
// Register for unmap first, in case some +load unmaps something
_dyld_register_func_for_remove_image(&unmap_image);
dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_2_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}
// objc4-437 objc-os.m
void _objc_init(void)
{
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
lock_init();
exception_init();
// Register for unmap first, in case some +load unmaps something
_dyld_register_func_for_remove_image(&unmap_image);
dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}
以上是三個版本的定義,最新版本 706 變得更簡潔了。category 附加到類上面是在 map_images/map_2_images(images這里表示二進制文件(可執(zhí)行文件或者動態(tài)鏈接庫 .iso 文件)編譯后的符號、代碼) 的時候發(fā)生的,map_images/map_2_images 兩個版本中,都在在方法里調(diào)用了 map_images_nolock 方法,在 map_images_nolock 函數(shù)中加載 images
// Find all images with Objective-C metadata.
hCount = 0;
i = infoCount;
while (i--) {
const headerType *mhdr = (headerType *)infoList[i].imageLoadAddress;
hi = _objc_addHeader(mhdr);
if (!hi) {
// no objc data in this entry
continue;
}
hList[hCount++] = hi;
if (PrintImages) {
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
_nameForHeader(mhdr),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
_objcHeaderIsReplacement(hi) ? " (replacement)" : "",
_objcHeaderOptimizedByDyld(hi)?" (preoptimized)" : "",
_gcForHInfo2(hi));
}
}
在加載完 images 后,便在方法末尾調(diào)用 _read_images函數(shù),我們在 _read_images函數(shù) 找到處理分類的相關(guān)代碼
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
// Do NOT use cat->cls! It may have been remapped.
class_t *cls = remapClass(cat->cls);
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
BOOL classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (isRealized(cls)) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
getName(cls), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
/* || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->isa, hi);
if (isRealized(cls->isa)) {
remethodizeClass(cls->isa);
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
getName(cls), cat->name);
}
}
}
}
首先,通過 getObjc2CategoryList(hi, &count) 獲取的 catlist 就是,在編譯器編譯時的 L_OBJC_LABEL_CATEGORY$。獲取到 category_t 列表后,開始遍歷 catlist,將 instanceMethods(實例方法)、protocols(協(xié)議)、instanceProperties(屬性)添加到類上,將 classMethods(類方法)、protocols(協(xié)議)添加到類的元類(meta class)上。在添加到元類時,有一段注釋 /* || cat->classProperties */ 可見并不會將 classProperties (類屬性)添加到元類上,不支持在分類給類添加類屬性。
addUnattachedCategoryForClass 函數(shù)
class_t *cls = remapClass(cat->cls);
addUnattachedCategoryForClass(cat, cls, hi);
static void addUnattachedCategoryForClass(category_t *cat, class_t *cls,
header_info *catHeader)
{
rwlock_assert_writing(&runtimeLock);
BOOL catFromBundle = (catHeader->mhdr->filetype == MH_BUNDLE) ? YES: NO;
// DO NOT use cat->cls!
// cls may be cat->cls->isa, or cat->cls may have been remapped.
NXMapTable *cats = unattachedCategories();
category_list *list;
list = NXMapGet(cats, cls);
if (!list) {
list = _calloc_internal(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = _realloc_internal(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (category_pair_t){cat, catFromBundle};
NXMapInsert(cats, cls, list);
}
實現(xiàn)代碼也比較簡單,大致內(nèi)容是:通過分類所屬類和分類獲取類的 category_list變量 list (分類列表),如果當前類未擁有分類,那么 list 不存在,便會初始化 list。如果 list 存在,便會用 list 從新初始化并將容量加 1,然后將分類添加到 list 中,最后將分類、類、分類列表插入到 NXMapTable 中。所以可見 addUnattachedCategoryForClass 函數(shù)是將 類和分類做了一個關(guān)聯(lián)存儲。
remethodizeClass 函數(shù)
remethodizeClass 是去處理分類方法添加的入口
// 刪除無關(guān)分類的代碼
static void remethodizeClass(struct class_t *cls)
{
category_list *cats;
BOOL isMeta;
rwlock_assert_writing(&runtimeLock);
isMeta = isMetaClass(cls);
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls))) {
BOOL vtableAffected = NO;
// Update methods, properties, protocols
attachCategoryMethods(cls, cats, &vtableAffected);
_free_internal(cats);
// Update method caches and vtables
flushCaches(cls);
if (vtableAffected) flushVtables(cls);
}
}
首先會執(zhí)行 unattachedCategoriesForClass(cls),以下是 unattachedCategoriesForClass 函數(shù)的實現(xiàn)
/***********************************************************************
* unattachedCategoriesForClass
* Returns the list of unattached categories for a class, and
* deletes them from the list.
* The result must be freed by the caller.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static category_list *unattachedCategoriesForClass(class_t *cls)
{
rwlock_assert_writing(&runtimeLock);
return NXMapRemove(unattachedCategories(), cls);
}
根據(jù)注釋和方法名,我們可以很清晰的知道 unattachedCategoriesForClass 做了什么,調(diào)用 NXMapRemove 函數(shù),NXMapRemove 函數(shù)以類為 key 從 NXMapTable 中刪除類映射的分類并返回類的分類列表,最終 unattachedCategoriesForClass 將 NXMapRemove 函數(shù)得到分類列表返回。
在執(zhí)行 unattachedCategoriesForClass 函數(shù)獲得分類列表后,將分類列表傳入 attachCategoryMethods 函數(shù)中,attachCategoryMethods 函數(shù)的實現(xiàn)如下
attachCategoryMethods(class_t *cls, category_list *cats,
BOOL *outVtablesAffected)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
BOOL isMeta = isMetaClass(cls);
method_list_t **mlists = _malloc_internal(cats->count * sizeof(*mlists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int i = cats->count;
BOOL fromBundle = NO;
while (i--) {
method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= cats->list[i].fromBundle;
}
}
attachMethodLists(cls, mlists, mcount, fromBundle, outVtablesAffected);
_free_internal(mlists);
}
此函數(shù)中,遍歷分類列表,如果不是元類將分類的實例方法列表 mlist 添加到 mlists 生成一個包含所有分類實例方法列表的實例方法列表,如果傳入的類是元類,處理的便是類方法。最后將 mlists 傳入 attachMethodLists 函數(shù)中,該函數(shù)的實現(xiàn)
static void
attachMethodLists(class_t *cls, method_list_t **lists, int count,
BOOL methodsFromBundle, BOOL *outVtablesAffected)
{
rwlock_assert_writing(&runtimeLock);
BOOL vtablesAffected = NO;
size_t listsSize = count * sizeof(*lists);
// Create or extend method list array
// Leave `count` empty slots at the start of the array to be filled below.
if (!cls->data->methods) {
// no bonus method lists yet
cls->data->methods = _calloc_internal(1 + count, sizeof(*lists));
} else {
size_t oldSize = malloc_size(cls->data->methods);
cls->data->methods =
_realloc_internal(cls->data->methods, oldSize + listsSize);
memmove(cls->data->methods + count, cls->data->methods, oldSize);
}
// Add method lists to array.
// Reallocate un-fixed method lists.
int i;
for (i = 0; i < count; i++) {
method_list_t *mlist = lists[i];
if (!mlist) continue;
// Fixup selectors if necessary
if (!isMethodListFixedUp(mlist)) {
mlist = _memdup_internal(mlist, method_list_size(mlist));
fixupMethodList(mlist, methodsFromBundle);
}
// Scan for vtable updates
if (outVtablesAffected && !vtablesAffected) {
uint32_t m;
for (m = 0; m < mlist->count; m++) {
SEL sel = method_list_nth(mlist, m)->name;
if (vtable_containsSelector(sel)) vtablesAffected = YES;
}
}
// Fill method list array
cls->data->methods[i] = mlist;
}
if (outVtablesAffected) *outVtablesAffected = vtablesAffected;
}
首先判斷類的方法總列表 methods 是否是為空,如果為空,以傳入的方法總列表 lists 中的元素(元素為方法列表)數(shù)量加一的容量以及 lists 的size 初始化 methods,如果 methods 不為空,便以原 methods 和 原 methods 的size 加上 lists 的 size 初始化,通過 memmove 宏方法將 methods 容量擴容增加 count (即 lists 中的方法列表個數(shù))并將原來的 methods 中的方法列表往后移 count 位,這樣原來的方法列表就被移至到 methods 中的后面部分。
對于 methods 方法總列表的處理也就結(jié)束了,接下來便是開始添加方法列表至 methods 中。遍歷傳入的方法總列表 lists,對方法列表 mlist 中的方法進行注冊等處理,最后添加到 methods 中。
最后的添加是將方法列表添加到 methods 的前面,這樣的添加方法使得即使 category 中存在和原來類一樣的方法也不會替換掉原來類的方法。調(diào)用方法時,會從 methods 的前面開始遍歷,當找到方法后就開始調(diào)用并終止查詢,所以在后面存在一樣的方法便不可能會被調(diào)用,這也就是我們所說的:category的方法會“覆蓋”掉原來類的同名方法。
category 與 + load 方法
創(chuàng)建以下文件,并調(diào)用 + load 方法
// Father.h
@interface Father : NSObject
+ (void)load;
@end
// Father.m
@implementation Father
+ (void)load {
NSLog(@"[Father load]");
}
// Father+Category.h
@interface Father (Category)
+ (void)load;
@end
// Father+Category.m
@implementation Father (Category)
+ (void)load {
NSLog(@"[Category load]");
}
// Father+Category2.h
@interface Father (Category2)
+ (void)load;
@end
// Father+Category2.m
@implementation Father (Category2)
+ (void)load {
NSLog(@"[Category2 load]");
}
以編譯文件順序為:Father、Father+Category、Father+Category2,根據(jù)上節(jié)講述的,我們可能會覺的打印的結(jié)果如下:
[Category2 load]
但其實正確的結(jié)果如下:
[Father load]
[Category load]
[Category2 load]
從 objc_init 中看 + load 的處理
這是為什么呢?說好的覆蓋呢?我們來看 runtime 入口方法 _objc_init
void _objc_init(void)
{
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
lock_init();
exception_init();
// Register for unmap first, in case some +load unmaps something
_dyld_register_func_for_remove_image(&unmap_image);
dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}
在 map_images 執(zhí)行完后,category 的方法也就添加到類上了,最后執(zhí)行
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
初始化 dyld 所需的依賴和注冊 load_images 回調(diào)通知,當 iamge 被加載就會通知 runtime 處理,該函數(shù)的實現(xiàn)如下
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
__private_extern__ const char *
load_images(enum dyld_image_states state, uint32_t infoCount,
const struct dyld_image_info infoList[])
{
BOOL found;
recursive_mutex_lock(&loadMethodLock);
// Discover load methods
rwlock_write(&runtimeLock);
found = load_images_nolock(state, infoCount, infoList);
rwlock_unlock_write(&runtimeLock);
// Call +load methods (without runtimeLock - re-entrant)
if (found) {
call_load_methods();
}
recursive_mutex_unlock(&loadMethodLock);
return NULL;
}
通過 load_images_nolock 函數(shù)查詢 images中是否有 + load 方法并收集 + load方法,該方法實現(xiàn)如下
load_images_nolock(enum dyld_image_states state,uint32_t infoCount,
const struct dyld_image_info infoList[])
{
BOOL found = NO;
uint32_t i;
i = infoCount;
while (i--) {
header_info *hi;
for (hi = FirstHeader; hi != NULL; hi = hi->next) {
const headerType *mhdr = (headerType*)infoList[i].imageLoadAddress;
if (hi->mhdr == mhdr) {
prepare_load_methods(hi);
found = YES;
}
}
}
return found;
}
使用 while 循環(huán)遍歷加載 iamges 中的 + load,其中處理便是 prepare_load_methods 這個函數(shù),我們看看這個函數(shù)的實現(xiàn)
void prepare_load_methods(header_info *hi)
{
size_t count, i;
rwlock_assert_writing(&runtimeLock);
class_t **classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
class_t *cls = remapClass(classlist[i]);
schedule_class_load(cls);
}
category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
// Do NOT use cat->cls! It may have been remapped.
class_t *cls = remapClass(cat->cls);
realizeClass(cls);
assert(isRealized(cls->isa));
add_category_to_loadable_list((Category)cat);
}
}
我們可以看到,首先被處理的是類的 + load 方法,將類和類的 + load 通過 schedule_class_load 函數(shù)中的 add_class_to_loadable_list 函數(shù)收集在 loadable_classes 中,然后才是處理分類中的 + load 方法,將分類和分類中的 + load方法收集在 loadable_categories 中。至此,+ load 收集準備工作也就完成了。
回到 load_images 函數(shù), 如果有 + load 方法的話,那么 load_images_nolock 則會返回 YES,那么接下來便會執(zhí)行 call_load_methods 函數(shù),調(diào)用所有收集的 + load 方法,我們來看看它的實現(xiàn)
void call_load_methods(void)
{
static BOOL loading = NO;
BOOL more_categories;
recursive_mutex_assert_locked(&loadMethodLock);
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
loading = NO;
}
在 do-while 循環(huán)中我們可以看到,首先執(zhí)行的是類的+ load 方法,然后是分類的 + load 方法 。
總結(jié)
runtime 會收集所有的 + load 方法并調(diào)用所有的 + load 方法,所以即使在分類中實現(xiàn) + load 方法也不會覆蓋類的+ load 方法。+ load 的調(diào)用順序,類總是在分類之前調(diào)用,而分類里的 + load 方法調(diào)用順序自然是以最后編譯的分類的 + load 方法為先。
后言
本次源碼版本是 objc4-437,附上下載入口 源碼下載。作為一只菜鳥,也有向往成為大神的夢想。閱讀源碼,有的時候讓自己很是頭疼,當慢慢反復(fù)閱讀后,感覺豁然開朗。本文若有錯誤之處還望指出,謝謝!