本文是Objective-C系列的第11篇,主要講述了及load
和initialize
兩個特殊方法的相關特性及其底層的實現。git
Objective-C(七)對象內存分析github
Objective-C(十二)關聯對象fetch
在講述以前,咱們先把該兩個方法經常使用到的一些知識點先列出。ui
根據下列順序,閱讀objc源碼便可。spa
測試用例源碼在**02-load-initialize**ssr
+load
+load
方法+load
方法@implementation BFPerson
+ (void)load
{
NSLog(@"+[BFPerson load]");
}
@end
@implementation BFBoy
+ (void)load
{
NSLog(@"+[BFBoy load]");
}
@end
複製代碼
輸出日誌:
14:21:44.542004+0800 Category[4483:5050573] +[BFPerson load]
14:21:44.542545+0800 Category[4483:5050573] +[BFBoy load]
load
方法+load
方法+load
方法
+load
方法調用順序與編譯順序一致@implementation BFBoy
+ (void)load
{ NSLog(@"+[BFBoy load]");}
@end
@implementation BFBoy (Handsome)
+ (void)load
{ NSLog(@"+[BFBoy load]--Handsome Cat");}
@end
@implementation BFBoy (Tall)
+ (void)load
{ NSLog(@"+[BFBoy load]--Tall Cat"); }
@end
複製代碼
輸出日誌:
14:24:09.679907+0800 Category[4565:5054709] +[BFPerson load]
14:24:09.680555+0800 Category[4565:5054709] +[BFBoy load]
14:24:09.680632+0800 Category[4565:5054709] +[BFPerson load]--Wrok Cat
14:24:09.680749+0800 Category[4565:5054709] +[BFBoy load]--Tall Cat
14:24:09.680845+0800 Category[4565:5054709] +[BFBoy load]--Handsome Cat
void prepare_load_methods(const headerType *mhdr)
{
//1.獲取非懶加載的類列表
classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
//1. 先將類及其父類的load ---> loadable_classes
schedule_class_load(remapClass(classlist[i]));
}
//2.獲取分類列表
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
// 2. 將分類的load ---> loadable_categories
add_category_to_loadable_list(cat);
}
}
複製代碼
調用load方法,即將上一步抽取出來的方法列表loadable_classes
、loadable_categories
,逐一調用便可。
+load
方法
+load
方法+load
方法+load
方法void call_load_methods(void)
{
do {
// 1. 會先迭代調用完全部的類的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. 依次調用全部分類的load方法
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
}
複製代碼
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod(); //獲取類中load的IMP
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
/* 1. loadable_class :Class load方法的結構體 1.1 class與load一一對應 1.2 struct loadable_class { Class cls; // may be nil IMP method; }; 2. loadable_classes 存放loadable_class列表 3. loadable_classes_used 當前load存放在loadable_class序號 4. loadable_classes 每次分配的內存爲 loadable_classes_allocated*2 + 16; 4.1 realloc void *realloc(void *ptr, size_t size) 從新調整以前調用 malloc 或 calloc 所分配的 ptr 所指向的內存塊的大小。 */
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++;
}
複製代碼
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));
}
/* 1. loadable_category :存放Category load方法的結構體 1.1 class與load一一對應 1.2 struct loadable_category { Category cat; // may be nil IMP method; }; 2. loadable_categories 存放loadable_category列表 3. loadable_categories_used 當前load存放在loadable_category序號 4. loadable_categories 分配的內存 4.1 realloc void *realloc(void *ptr, size_t size) 從新調整以前調用 malloc 或 calloc 所分配的 ptr 所指向的內存塊的大小。 4.2 每次分配爲 loadable_categories_allocated*2 + 16; 即上次分配後,一直能使用到(上次的2倍+16)次,纔會再次分配 */
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++;
}
複製代碼
(*load_method)(cls, SEL_load);
複製代碼
因爲+initialize
是在類第一次接收到消息是調用,咱們調用**[BFPerson alloc];**時,就會調用。
咱們根據彙編debug獲得了下面流程:
> 1. objc_msgSend
> 2. objc_msgSend_uncached
> 3._class_lookupMethodAndLoadCache3
> 4. lookUpImpOrForward
> 5. _class_initialize
5.1>> _setThisThreadIsInitializingClass(objc_class*)
5.1.1>>> _fetchInitializingClassList
5.2>> CALLING_SOME+initialize_METHOD:
5.2.1>>> objc_msgSend(cls, SEL_initialize)
//這一步會打印 +[BFPerson initialize]--Study Cat
5.3>> lockAndFinishInitializing
複製代碼
而後,咱們去objc4源碼挖礦。
咱們發現,1
、2
在源碼中都是彙編,3
是可讀的C代碼。就從3
開始。
測試用例源碼在**02-load-initialize**
initialize
@implementation BFPerson
+ (void)initialize
{
NSLog(@"+[BFPerson initialize]");
}
@end
@implementation BFBoy
+ (void)initialize
{
NSLog(@"+[BFBoy initialize]");
}
@end
複製代碼
調用
[BFBoy alloc];
複製代碼
此時打印以下:
13:44:35.437131+0800 Category[3527:4997267] +[BFPerson initialize]
13:44:35.437240+0800 Category[3527:4997267] +[BFBoy initialize]
+initialize
,不調用類自己的+initialize
@implementation BFPerson
+ (void)initialize
{
NSLog(@"+[BFPerson initialize]");
}
@end
@implementation BFPerson (Work)
+ (void)initialize
{
NSLog(@"+[BFBoy initialize]");
}
@end
複製代碼
調用:
[BFBoy alloc];
複製代碼
打印輸出
13:45:35.437121+0800 Category[3527:4997267] +[BFPerson initialize]--Wrok Cat
initialize
多個子類均未實現,但父類實現+initialize
方法,會屢次調用+initialize
。
@implementation BFPerson
+ (void)initialize
{
NSLog(@"+[BFPerson initialize]");
}
@end
@implementation BFBoy
@end
@implementation BFGirl
@end
複製代碼
調用:
[BFBoy alloc];
[BFGirl alloc];
複製代碼
打印輸出
13:52:44.968194+0800 Category[3786:5010745] +[BFPerson initialize]
13:52:44.968327+0800 Category[3786:5010745] +[BFPerson initialize]
13:52:44.968427+0800 Category[3786:5010745] +[BFPerson initialize]
類的初始化加載,會先調用父類+initialize
方法,再調用類自己的+initialize
。
//僞代碼
BOOL personInitialized = NO;
BOOL boyInitialized = NO;
if (!boyInitialized) {
//1. 父類調用
if (!personInitialized) {
objc_msgSend([BFPerson class], @selector(initialize));
personInitialized = YES;
}
//2. 類自己調用
objc_msgSend([BFBoy class], @selector(initialize));
boyInitialized = YES;
}
複製代碼
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
....
//須要初始化,且類未進行初始化
if (initialize && !cls->isInitialized()) {
//initialize
_class_initialize (_class_getNonMetaClass(cls, inst));
}
....
}
複製代碼
+initialize
調用void _class_initialize(Class cls)
{
// 1. 先調用父類 initialization
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
....
//2. 給類發送initialize消息
callInitialize(cls);
....
//3. 完成類的初始化
lockAndFinishInitializing(cls, supercls);
...
}
複製代碼