博客連接 從源碼理解Category、load和initialize數組
Objective-C中的Category就是對裝飾模式的一種具體實現。它的主要做用是在不改變原有類的前提下,動態地給這個類添加一些方法。緩存
先看一段代碼,後面的關於Category分析都是基於如下代碼:bash
// CategoryTestModel.h
@interface CategoryTestModel : NSObject
- (void)ins_method1;
+ (void)cls_method1;
@end
@interface CategoryTestModel(NN)
- (void)ins_method2;
- (void)ins_method22;
+ (void)cls_method2;
@end
@interface CategoryTestModel(Nero)
- (void)ins_method3;
+ (void)cls_method3;
@end
// CategoryTestModel.m
@implementation CategoryTestModel
- (void)ins_method1 {
NSLog(@"%s", __func__);
}
+ (void)cls_method1 {
NSLog(@"%s", __func__);
}
@end
@implementation CategoryTestModel(NN)
- (void)ins_method2 {
NSLog(@"%s", __func__);
}
- (void)ins_method22 {
NSLog(@"%s", __func__);
}
+ (void)cls_method2 {
NSLog(@"%s", __func__);
}
@end
@implementation CategoryTestModel(Nero)
- (void)ins_method3 {
NSLog(@"%s", __func__);
}
+ (void)cls_method3 {
NSLog(@"%s", __func__);
}
@end
複製代碼
上面的代碼聲明瞭一個CategoryTestModel
的類以及NN
和Nero
兩個分類,接着咱們使用以下命令將這些代碼轉化成C++代碼: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc CategoryTestModel.m
app
在C++代碼中,咱們會發現有如下關鍵代碼:iphone
// NN分類
static struct _category_t _OBJC_$_CATEGORY_CategoryTestModel_$_NN __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"CategoryTestModel",
0, // &OBJC_CLASS_$_CategoryTestModel,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryTestModel_$_NN,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CategoryTestModel_$_NN,
0,
0,
};
// Nero分類
static struct _category_t _OBJC_$_CATEGORY_CategoryTestModel_$_Nero __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"CategoryTestModel",
0, // &OBJC_CLASS_$_CategoryTestModel,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryTestModel_$_Nero,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CategoryTestModel_$_Nero,
0,
0,
};
複製代碼
從源碼中能夠知道,分類轉化成了_category_t
類型的結構體,而且有幾個分類,就會對應生成幾個這樣的結構體。這裏有一點要說明一下,在objc的代碼中也能夠找到關於分類的結構體介紹,結構體名叫category_t
,併成員變量稍微有點差別,可是不影響對底層實現的學習。函數
_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;
};
複製代碼
從代碼中咱們能夠看出_category_t
結構體中存放着類名,主類名,對象方法列表,類方法列表,協議列表,以及屬性列表。測試
在咱們聲明的分類中,只有對象方法和類方法,因此咱們看一下_method_list_t
。 _method_list_t
的定義以下:ui
// 對象方法存儲
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryTestModel_$_NN __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"ins_method2", "v16@0:8", (void *)_I_CategoryTestModel_NN_ins_method2},
{(struct objc_selector *)"ins_method22", "v16@0:8", (void *)_I_CategoryTestModel_NN_ins_method22}}
};
// 類方法存儲
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_CategoryTestModel_$_NN __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"cls_method2", "v16@0:8", (void *)_C_CategoryTestModel_NN_cls_method2}}
};
複製代碼
經過上面的一系列源碼,咱們知道了以下幾點:this
_category_t
的結構體,咱們在分類中聲明的方法、屬性等都會存在對應的字段中;_category_t
結構體。到這裏就難免會出現一個疑問: 在咱們將代碼轉化成C++代碼後,一個類有其主類_class_t
還有N個_category_t
的分類。主類方法存在主類的結構中,分類方法存在分類的結構體中。咱們打印其方法列表的時候,分類方法也會打印出來,另外咱們在平常開發中,有時也會使用分類去覆蓋掉主類實現的方法,那這又是爲何?上面所講的只是編譯期的過程,Category的實現還依賴於Runtime。
這裏插一個題外話,在平常開發的時候,在一些比較麻煩的時候咱們可能會使用方法交叉,咱們也知道方法交叉是比較危險的手段,仍是少用爲妙。而上面提到的使用分類去覆蓋掉主類實現的方法也是能減小方法交叉的使用。
這裏使用objc4-750.1
的源代碼,你能夠經過這裏下載。在objc-runtime-new.m
中找到一個_read_images
的函數,因爲代碼很長,這裏只貼出關於分類的實現:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// 這裏只顯示category的實現
// Discover categories.
for (EACH_HEADER) {
// 二維數組存在分類結構體變量
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) {
// 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.
bool classExists = NO;
// 分類中存在對象方法,協議或者屬性,
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 給主類添加獨立的分類
addUnattachedCategoryForClass(cat, cls, hi);
// 類是否實現
if (cls->isRealized()) {
// 對類進行方法化
remethodizeClass(cls);
classExists = YES;
}
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");
}
複製代碼
_getObjc2CategoryList
函數獲取到分類列表;remethodizeClass(cls)
函數對類對象(分類的主類)從新方法化;remethodizeClass(cls->ISA());
對類對象的元類從新方法化;能夠看到最終都調用了remethodizeClass
函數,接着咱們看一下remethodizeClass
的實現:
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// 附加分類
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
複製代碼
remethodizeClass
的實現主要依賴於attachCategories
,從函數名咱們能夠知道這個函數,就是用來給類或者元類添加分類中存放的數據。attachCategories
的實現以下:
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
// 方法列表 [[方法],[方法,方法],[方法]]
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));
// Count backwards through cats to get newest categories first
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;
}
}
// rw表明類對象,裏面有一個方法列表和屬性列表的字段
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 將上面合併後的分類方法合併到類對象的方法列表中,並釋放mlists
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()) {
// 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的做用就是內存移動,將原來的數組向後拖動oldCount * sizeof(array()->lists[0])個位置
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// memcpy的做用是內存拷貝,將新數組拷貝到前面空出的位置
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]));
}
}
複製代碼
關於上述關於合併的實現分紅了三種狀況,可是思想都接近,簡單的說就是數組合並。分析這段代碼是爲了解釋爲何咱們能夠在分類中重寫方法來覆蓋主類的實現。
這裏咱們就以分類方法在many lists -> many lists
的狀況下來分析一下這個流程。
memmove
,將原來的方法列表向後拖動oldCount * sizeof(array()->lists[0])個位置;memcpy
,將分類方法列表拷貝在前面空出的位置。用一張圖表示下上面的狀況:
這裏再總結一下Categoty的一些知識點:
_category_t
的結構體,咱們在分類中聲明的方法、屬性等都會存在對應的字段中;可是有兩個方法比較特殊,分別是load
和initialize
,它們的執行結果和通常方法有區別,因此單獨拿出來分析。
首先看一段代碼:
// FatherA
@implementation FatherA
+ (void)load {
NSLog(@"%s", __func__);
}
@end
@implementation FatherA(NN)
+ (void)load {
NSLog(@"%s", __func__);
}
@end
@implementation FatherA(Nero)
+ (void)load {
NSLog(@"%s", __func__);
}
@end
// SonA
@implementation SonA
+ (void)load {
NSLog(@"%s", __func__);
}
@end
@implementation SonA(NN)
+ (void)load {
NSLog(@"%s", __func__);
}
@end
@implementation SonA(Nero)
+ (void)load {
NSLog(@"%s", __func__);
}
@end
// FatherB
@implementation FatherB
+ (void)load {
NSLog(@"%s", __func__);
}
// SonB
@implementation SonB
+ (void)load {
NSLog(@"%s", __func__);
}
複製代碼
編譯順序以下:
執行結果以下:
以FatherA
爲例,咱們知道load
方法會在runtime加載類、分類的時候進行調用,根據上面提到的分類會覆蓋主類的同名方法(這裏並非真的覆蓋,而是優先於主類先調用),load
函數應該只是被調用一次,可是經過上面的代碼咱們看到load
方法被調用了三次,這是爲何呢?
load
方法的實如今objc-runtime-new.m
中找到一個load_images
的函數,其實現以下:
// load_images
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
複製代碼
上面代碼的實現主要依賴於prepare_load_methods
和call_load_methods
。prepare_load_methods
用來查找load
方法並肯定其調用順序,call_load_methods
用來調用load
方法。
咱們先看一下prepare_load_methods
的實現:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// 獲取全部的類,classlist中類的順序按照誰先編譯,誰就在數組的前面
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 規劃類的加載,即肯定+load方法的調用順序
schedule_class_load(remapClass(classlist[i]));
}
// 獲取全部的分類
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
// 將實現了+load方法的分類添加到loadable_categories數組的最後面
add_category_to_loadable_list(cat);
}
}
複製代碼
prepare_load_methods
的實現能夠分爲兩個步驟:
schedule_class_load
;add_category_to_loadable_list
, add_category_to_loadable_list
將實現了+load
方法的分類添加到loadable_categories
數組的最後面,loadable_categories
用來在call_load_methods
進行分類+load
方法的調用。上面代碼中,關於classlist
和categorylist
這兩個數組中的元素的順序是根據類或者分類被編譯的順序分別進入對應數組中的。
肯定類load
方法的調用順序,依賴於schedule_class_load
,其實現以下:
// schedule_class_load是一個遞歸函數
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
// 沿着cls的父類一直往上查找知道NSObject
schedule_class_load(cls->superclass);
// 將實現了+load的cls添加到loadable_classes的最後面
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
複製代碼
schedule_class_load
是一個遞歸函數,它會沿着cls的父類一直往上查找知道NSObject,接着調用add_class_to_loadable_list
將實現了+load
方法的cls添加到loadable_classes
的最後面。這也就解釋了爲何父類的load
方法爲何會優先於子類的load
方法被調用。
接着再看一下add_class_to_loadable_list
的實現:
// 這個結構體中的method專門存放類中的+load方法
struct loadable_class {
Class cls; // may be nil
IMP method;
};
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method if (PrintLoading) { _objc_inform("LOAD: class '%s' scheduled for +load", cls->nameForLogging()); } if (loadable_classes_used == loadable_classes_allocated) { loadable_classes_allocated = loadable_classes_allocated*2 + 16; loadable_classes = (struct loadable_class *) realloc(loadable_classes, loadable_classes_allocated * sizeof(struct loadable_class)); } loadable_classes[loadable_classes_used].cls = cls; loadable_classes[loadable_classes_used].method = method; loadable_classes_used++; } 複製代碼
這個代碼的做用就是經過傳入的cls
生成一個對應的loadable_class
結構體,並將這個結構體加在loadable_classes
的最後面,loadable_class
這個結構體中的method
專門存放類中的+load
方法。
分類load方法的調用順序就是遍歷獲取到的分類列表,經過add_category_to_loadable_list
將實現了+load
方法的分類添加到loadable_categories
數組的最後面。add_category_to_loadable_list
的實現以下:
// 這個結構體中的method專門存放分類中的+load方法
struct loadable_category {
Category cat; // may be nil
IMP method;
};
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method if (!method) return; if (PrintLoading) { _objc_inform("LOAD: category '%s(%s)' scheduled for +load", _category_getClassName(cat), _category_getName(cat)); } if (loadable_categories_used == loadable_categories_allocated) { loadable_categories_allocated = loadable_categories_allocated*2 + 16; loadable_categories = (struct loadable_category *) realloc(loadable_categories, loadable_categories_allocated * sizeof(struct loadable_category)); } loadable_categories[loadable_categories_used].cat = cat; loadable_categories[loadable_categories_used].method = method; loadable_categories_used++; } 複製代碼
add_category_to_loadable_list
函數和add_class_to_loadable_list
函數的實現邏輯很類似,這個就不作說明了。
這裏總結一下load
方法的調用順序:
+load
方法的類來講,其load
方法的調用順序是先編譯的類的父類 > 先編譯的類 > 後編譯的類的父類 > 後編譯的類;+load
方法的類來講,其load
方法的調用順序是誰先被編譯,誰就優先被調用。當肯定好了load
的調用順序了之後,就須要調用load
方法。
接着咱們看一下call_load_methods
的實現:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more while (loadable_classes_used > 0) { // 調用類的load方法 call_class_loads(); } // 2. Call category +loads ONCE // 調用分類的load方法 more_categories = call_category_loads(); // 3. Run more +loads if there are classes OR more untried categories } while (loadable_classes_used > 0 || more_categories); objc_autoreleasePoolPop(pool); loading = NO; } 複製代碼
call_load_methods
函數也是分爲兩個步驟:
call_class_loads
調用類的load
方法;call_category_loads
調用分類的load
方法。call_class_loads
的實現以下:
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
// 遍歷loadable_classes列表調用+load方法,數組中越前面的元素越先被調用
// 根據schedule_class_load的遞歸查找還能夠知道,父類的load方法要先於子類進行調用
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
// 取出類中的load方法
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
// 使用指針進行調用
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
複製代碼
call_class_loads
函數經過遍歷遍歷loadable_classes
列表調用+load
方法,數組中越前面的元素越先被調用。經過load_method_t
能夠獲取類的load
方法,load_method_t
的定義爲:
typedef void(*load_method_t)(id, SEL);
複製代碼
load_method_t
是指向函數的指針,至關於返回的是一個函數地址。獲取到函數指針後,使用(*load_method)(cls, SEL_load);
調用load
方法。因此說,load方法的調用並非使用消息發送機制,而是直接使用函數指針調用。
call_category_loads
用來調用分類的load
方法,其實現以下:
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
// 取出分類中的load方法,返回值是一個函數地址
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
// 使用指針進行調用
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
// 如下代碼是一些釋放代碼,能夠忽略
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list. if (used) { loadable_categories = cats; loadable_categories_used = used; loadable_categories_allocated = allocated; } else { if (cats) free(cats); loadable_categories = nil; loadable_categories_used = 0; loadable_categories_allocated = 0; } if (PrintLoading) { if (loadable_categories_used != 0) { _objc_inform("LOAD: %d categories still waiting for +load\n", loadable_categories_used); } } return new_categories_added; } 複製代碼
這裏再總結一下load方法的一些知識點:
objc_msgSend
的方式。initialize
的測試代碼以下:
// FatherA
@implementation FatherA
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@implementation FatherA(NN)
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@implementation FatherA(Nero)
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
// SonA
@implementation SonA
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@implementation SonA(NN)
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
@implementation SonA(Nero)
+ (void)initialize {
NSLog(@"%s", __func__);
}
@end
// FatherB
@implementation FatherB
+ (void)initialize {
NSLog(@"%s", __func__);
}
// SonB
@implementation SonB
+ (void)initialize {
NSLog(@"%s", __func__);
}
複製代碼
咱們知道initialize
方法會在類第一次接收到消息的時候被調用,因此咱們使用以下代碼進行測試:
// 測試代碼
[SonA alloc];
[SonA alloc];
[SonA alloc];
[FatherB alloc];
複製代碼
執行結果以下:
經過上面的代碼能夠知道:
initialize
方法的實如今objc-runtime-new.m
中找到一個lookUpImpOrForward
的函數,其中有以下關鍵代碼:
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172 } 複製代碼
在方法調用過程當中,若是類沒有被初始化的時候,會調用_class_initialize
對類進行初始化,其中有兩塊關鍵代碼:
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void _class_initialize(Class cls)
{
...
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
// 沿着cls的父類一直往上查找知道NSObject
_class_initialize(supercls);
}
...
// 使用objc_msgSend調用initialize方法
callInitialize(cls);
...
}
複製代碼
_class_initialize
方法會進行遞歸調用,由此能夠確保父類優先於子類初始化。接着調用callInitialize
函數,其實現以下:
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
複製代碼
因此initialize
的調用方式是objc_msgSend
,它和普通方法同樣是由Runtime經過發消息的形式。
這裏再總結一下initialize方法的一些知識點:
initialize
方法只執行一次,避免屢次執行可能帶來的反作用時,咱們可使用下面的代碼來實現:+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
複製代碼
最後總結一下load和initialize區別
/ | load | initialize |
---|---|---|
調用時機 | 加載到runtime時 | 收到第一條消息時,可能永遠不調用 |
調用本質 | 函數地址調用 | objc_msgSend發送消息 |
調用順序 | 父類 > 類 > 分類 | 父類 > 類 |
調用次數 | 一次 | 一次或者屢次 |
分類的中實現 | 類和分類都執行 | "覆蓋"主類中的實現 |