一 Category基本使用
二 Category的底層結(jié)構(gòu)
三 Category的加載處理流程
四 Category的load方法講解
五 Category的initialize方法講解
Category也叫分類或類別,是OC提供的一種擴(kuò)展類的方式。不管是自定義的類還是系統(tǒng)的類,我們都可以通過(guò)Category給原有類擴(kuò)展方法(實(shí)例方法和類方法都可以),而且擴(kuò)展的方法和原有的方法的調(diào)用方式是一模一樣的。比如我項(xiàng)目中經(jīng)常需要統(tǒng)計(jì)一個(gè)字符串中字母的個(gè)數(shù),但是系統(tǒng)沒(méi)有提供這個(gè)方法,那我們就可以用Category給NSString類擴(kuò)展一個(gè)方法,然后只需引入Category的頭文件就可以和調(diào)用系統(tǒng)方法一樣來(lái)調(diào)用擴(kuò)展的方法
一 Category基本使用
下面我們來(lái)看一下Category的基本使用示例:
#import <Foundation/Foundation.h>
@interface People : NSObject
- (void)talk;
@end
#import "People.h"
@implementation People
- (void)talk{
NSLog(@"%s:can I speak?",__func__);
}
@end
#import "People.h"
@interface People (Speak)
-(void)speak;
@end
#import "People+Speak.h"
@implementation People (Speak)
-(void)speak{
NSLog(@"%s: I can speak",__func__);
}
@end
#import "People.h"
@interface People (Eat)
-(void)eat;
@end
#import "People+Eat.h"
@implementation People (Eat)
-(void)eat{
NSLog(@"%s: I can eat food",__func__);
}
@end
#import <Foundation/Foundation.h>
#import "People.h"
#import "People+Speak.h"
#import "People+Eat.h"
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *people = [[People alloc] init];
[people talk];
[people speak];
[people eat];
}
return 0;
}
output:
2019-02-07 13:52:08.128350+0800 thread[1362:24336] -[People talk]:can I speak?
2019-02-07 13:52:08.128735+0800 thread[1362:24336] -[People(Speak) speak]: I can speak
2019-02-07 13:52:08.128778+0800 thread[1362:24336] -[People(Eat) eat]: I can eat food
分類的使用是非常簡(jiǎn)單的,為什么給一個(gè)類添加的分類而且分類的方法和原有的方法的調(diào)用方式是一模一樣的都能通過(guò)這個(gè)類的對(duì)象進(jìn)行調(diào)用呢,我們一起來(lái)探究一下。
二 Category的底層結(jié)構(gòu)
我們把People+Speak.m
底層轉(zhuǎn)換成C++來(lái)看一下
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc People+Speak.m
#ifndef _REWRITER_typedef_People
#define _REWRITER_typedef_People
typedef struct objc_object People;
typedef struct {} _objc_exc_People;
#endif
struct People_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
static void _I_People_Speak_speak(People * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_People_Speak_8c87eb_mi_0,__func__);
}
// @end
struct _prop_t {
const char *name;
const char *attributes;
};
struct _protocol_t;
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods;
const struct method_list_t *class_methods;
const struct method_list_t *optionalInstanceMethods;
const struct method_list_t *optionalClassMethods;
const struct _prop_list_t * properties;
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name;
const char *type;
unsigned int alignment;
unsigned int size;
};
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
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;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)
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_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"speak", "v16@0:8", (void *)_I_People_Speak_speak}}
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_People;
//根據(jù)分類的定義,給_category_t賦值
static struct _category_t _OBJC_$_CATEGORY_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"People",
0, // &OBJC_CLASS_$_People,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak,
0,
0,
0,
};
static void OBJC_CATEGORY_SETUP_$_People_$_Speak(void ) {
_OBJC_$_CATEGORY_People_$_Speak.cls = &OBJC_CLASS_$_People;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_People_$_Speak,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_People_$_Speak,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我們看到最后會(huì)轉(zhuǎn)成 _category_t 這么一個(gè)結(jié)構(gòu)體
2.1 分類的結(jié)構(gòu)
struct category_t {
const char *name; //類名稱
struct _class_t *cls;
const struct _method_list_t *instance_methods; //對(duì)象方法列表
const struct _method_list_t *class_methods;//類方法列表
const struct _protocol_list_t *protocols; //協(xié)議列表
const struct _prop_list_t *properties; //屬性列表
};
通過(guò)分類結(jié)構(gòu)我們可以看到,分類里可以添加實(shí)例方法,類方法,遵循協(xié)議,定義屬性。我們看到當(dāng)我們編譯完一個(gè)分類,它的所有信息都放到下面這個(gè)結(jié)構(gòu)體中了
static struct _category_t _OBJC_$_CATEGORY_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"People",
0, // &OBJC_CLASS_$_People,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak,
0,
0,
0,
};
分類底層結(jié)構(gòu)就是個(gè)結(jié)構(gòu)體,但是怎么讓一個(gè)類的對(duì)象調(diào)用它的方法呢,下面我們來(lái)看一下。
三 Category的加載處理流程
分類的加載處理流程主要有下面三步:
1.通過(guò)Runtime加載某個(gè)類的所有Category數(shù)據(jù)
2.把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中 后面參與編譯的Category數(shù)據(jù),會(huì)在數(shù)組的前面
3.將合并后的分類數(shù)據(jù)(方法、屬性、協(xié)議),插入到類原來(lái)數(shù)據(jù)的前面
下面我們通過(guò)源碼,來(lái)看它每一步都是如何實(shí)現(xiàn)的。
首先我們從runtime初始化函數(shù)開(kāi)始看
步驟1
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_images, load_images, unmap_image);
}
步驟2
接著我們來(lái)到 &map_images讀取模塊(images這里代表模塊),來(lái)到map_images_nolock函數(shù)中找到_read_images函數(shù),在_read_images函數(shù)中我們找到分類相關(guān)代碼
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
void
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;
}
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
*****省略******
// 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);
}
}
}
}
}
通過(guò)_getObjc2CategoryList函數(shù)獲取到分類列表之后,
進(jìn)行遍歷,獲取其中的方法,協(xié)議,屬性等。
可以看到最終都調(diào)用了remethodizeClass(cls);函數(shù)。我們來(lái)到remethodizeClass(cls)
步驟3
attachCategories函數(shù)接收了類對(duì)象cls和分類數(shù)組cats,
如我們一開(kāi)始寫的代碼所示,一個(gè)類可以有多個(gè)分類。
之前我們說(shuō)到分類信息存儲(chǔ)在category_t結(jié)構(gòu)體中,
那么多個(gè)分類則保存在category_list中
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
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);
}
}
步驟4
1 首先根據(jù)方法列表,屬性列表,協(xié)議列表,malloc分配內(nèi)存,
根據(jù)多少個(gè)分類以及每一塊方法需要多少內(nèi)存來(lái)分配相應(yīng)的內(nèi)存地址。
2 然后從分類數(shù)組里面往三個(gè)數(shù)組里面存放分類數(shù)組里面存放的分類方法,
屬性以及協(xié)議放入對(duì)應(yīng)mlist、proplists、protolosts數(shù)組中,
這三個(gè)數(shù)組放著所有分類的方法,屬性和協(xié)議。
3 之后通過(guò)類對(duì)象的data()方法,拿到類對(duì)象的class_rw_t結(jié)構(gòu)體rw,
在class結(jié)構(gòu)中我們介紹過(guò),class_rw_t中存放著類對(duì)象的方法,屬性和協(xié)議等數(shù)據(jù),rw結(jié)構(gòu)體通過(guò)類對(duì)象的data方法獲取,
所以rw里面存放這類對(duì)象里面的數(shù)據(jù)。
4 最后分別通過(guò)rw調(diào)用方法列表、屬性列表、協(xié)議列表的attachList函數(shù),
將所有的分類的方法、屬性、協(xié)議列表數(shù)組傳進(jìn)去,
我們可以猜測(cè)在attachList方法內(nèi)部將分類和本類相應(yīng)的對(duì)象方法,屬性,和協(xié)議進(jìn)行了合并
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
//根據(jù)每個(gè)分類中的方法列表,屬性列表,協(xié)議列表分配內(nèi)存
// 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]; //遍歷分類數(shù)組
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist; //將所有分類中的所有方法存入mlists [ [method_t,method_t] [method_t,method_t] ...... ]
fromBundle |= entry.hi->isBundle();
}
//所有屬性
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//所有協(xié)議
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//取出類對(duì)象
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
將所有分類的對(duì)象方法,附加到類對(duì)象的方法列表
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);
}
步驟5 方法合并
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])); //原數(shù)據(jù)后移
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0])); //拷貝新數(shù)據(jù)到空出來(lái)的內(nèi)存
}
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]));
}
}
我們可以看到分類的方法屬性協(xié)議會(huì)追加到原來(lái)類的方法屬性協(xié)議列表的前面,這也就是說(shuō)如果一個(gè)類和它的分類有相同的方法,它的分類的方法會(huì)先被調(diào)用
。
上面的源碼跟讀完了,主要源碼流程如下:
源碼解讀順序
objc-os.mm
_objc_init
map_images
map_images_nolock
objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy
到此我們總結(jié)下category整個(gè)流程:我們每創(chuàng)建一個(gè)分類,在編譯時(shí)都會(huì)生成category_t
這樣一個(gè)結(jié)構(gòu)體并將分類的方法列表等信息存入_category_t這個(gè)結(jié)構(gòu)體。在編譯階段分類的相關(guān)信息和本類的相關(guān)信息是分開(kāi)的。等到運(yùn)行階段,會(huì)通過(guò)runtime加載某個(gè)類的所有Category數(shù)據(jù),把所有Category的方法、屬性、協(xié)議數(shù)據(jù)分別合并到一個(gè)數(shù)組中,然后再將分類合并后的數(shù)據(jù)插入到本類的數(shù)據(jù)的前面
到此分類原理就講完了,接下來(lái)我們?cè)僦v解下category中的兩個(gè)方法。
四 Category的load方法講解
4.1 load基本使用
我們來(lái)看一下load基本使用示例
#import <Foundation/Foundation.h>
@interface People : NSObject
@end
#import "People.h"
@implementation People
+ (void)load {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface People (Speak)
@end
#import "People+Speak.h"
@implementation People (Speak)
+ (void)load {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface People (Eat)
@end
#import "People+Eat.h"
@implementation People (Eat)
+ (void)load {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface SubPeople : People
@end
#import "SubPeople.h"
@implementation SubPeople
+ (void)load
{
NSLog(@"%s",__func__);
}
@end
#import <Foundation/Foundation.h>
//#import "People.h"
//#import "People+Speak.h"
//#import "People+Eat.h"
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// People *people = [[People alloc] init];
}
return 0;
}
output:
2019-02-07 20:53:50.896337+0800 thread[3442:174464] +[People load]
2019-02-07 20:53:50.896816+0800 thread[3442:174464] +[SubPeople load]
2019-02-07 20:53:50.896910+0800 thread[3442:174464] +[People(Speak) load]
2020-02-07 20:53:50.896971+0800 thread[3442:174464] +[People(Eat) load]
我們可以發(fā)現(xiàn),我僅僅重寫實(shí)現(xiàn)了load方法,只要編譯完運(yùn)行,就會(huì)調(diào)用load方法。即使我沒(méi)有任何引用使用的地方。為什么呢,接下來(lái)我們看一下
4.2 load調(diào)用原理
+load方法會(huì)在runtime加載類、分類時(shí)調(diào)用
每個(gè)類、分類的+load,在程序運(yùn)行過(guò)程中只調(diào)用一次
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_images, load_images, unmap_image);
}
//跟進(jìn)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
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
//跟進(jìn)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) {
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);
objc_autoreleasePoolPop(pool);
loading = NO;
}
因?yàn)樵诔绦蜻\(yùn)行時(shí)就調(diào)用,所以我們也是從runtime初始化方法開(kāi)始,load調(diào)用順序如下
4.3調(diào)用順序
1.先調(diào)用類的+load
按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
調(diào)用子類的+load之前會(huì)先調(diào)用父類的+load
2.再調(diào)用分類的+load
按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
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) { //先調(diào)用類的+load
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads(); //再調(diào)用分類的+load
// 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_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
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.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method; //取出classes的method
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load); //直接調(diào)用
}
// Destroy the detached list.
if (classes) free(classes);
}
+load方法是根據(jù)方法地址直接調(diào)用,并不是經(jīng)過(guò)objc_msgSend函數(shù)調(diào)用的。下面我們?cè)倏匆幌耰nitialize方法。如果是手動(dòng)調(diào)用load,[people load]
,那就會(huì)走消息發(fā)送機(jī)制。
五 Category的initialize方法講解
5.1 initialize基本使用
我們把上面load的例子直接改成initialize就好了,如下:
#import <Foundation/Foundation.h>
@interface People : NSObject
@end
#import "People.h"
@implementation People
+ (void) initialize {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface People (Speak)
@end
#import "People+Speak.h"
@implementation People (Speak)
+ (void) initialize {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface People (Eat)
@end
#import "People+Eat.h"
@implementation People (Eat)
+ (void) initialize {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface SubPeople : People
@end
#import "SubPeople.h"
@implementation SubPeople
+ (void)initialize
{
NSLog(@"%s",__func__);
}
@end
#import <Foundation/Foundation.h>
#import "People.h"
#import "People+Speak.h"
#import "People+Eat.h"
#import "SubPeople.h"
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
SubPeople *people = [[SubPeople alloc] init];
SubPeople *people2 = [[SubPeople alloc] init];
}
return 0;
}
output:
2019-02-07 20:49:47.531310+0800 thread[3376:171866] +[People(Eat) initialize]
2019-02-07 20:49:47.531748+0800 thread[3376:171866] +[SubPeople initialize]
最終輸出結(jié)果是People eat分類的initialize方法,然后又調(diào)用SubPeople自己的initialize方法,我們生成了兩個(gè)對(duì)象,結(jié)果輸出結(jié)果就一次,這是為什么呢,我們接著來(lái)看。
5.2 initialize調(diào)用原理
+initialize方法會(huì)在類第一次接收到消息時(shí)調(diào)用
源碼調(diào)用流程如下:
因?yàn)槭窃陬惖谝淮谓邮盏较r(shí)調(diào)用,那肯定是在objc_msgSend方法內(nèi)部調(diào)用的,但是objc_msgSend是匯編代碼,它的實(shí)現(xiàn)在objc-msg-arm64.s
文件下,它的深層實(shí)現(xiàn)我們是看不到的,但是既然是通過(guò)消息機(jī)制,那么根據(jù)消息發(fā)送流程,首先要找到方法,然后才能調(diào)用方法,那我們就看一下在查找方法或者調(diào)用方法時(shí)有沒(méi)有調(diào)用initialize,首先我們看查找方法
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
----- 省略------
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// 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
}
----- 省略------
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO; //是否初始化過(guò)
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) { //如果父類未初始化過(guò)
_class_initialize(supercls); //初始化父類
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) { //如果自己未初始化
cls->setInitializing(); //初始化自己
reallyInitialize = YES;
}
}
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
}
}
// 發(fā)送Initialize消息
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
我們跟蹤源碼發(fā)現(xiàn)在查找方法的過(guò)程中就已經(jīng)處理過(guò)initialize方法了。
根據(jù)最后的源碼我們可以看出initialize調(diào)用順序從如下:
1 先調(diào)用父類的+initialize,再調(diào)用子類的+initialize
2 先初始化父類,再初始化子類,每個(gè)類只會(huì)初始化1次
到這里講完,我們也就能解釋基本示例里面的輸出結(jié)果了,因?yàn)閕nitialize只會(huì)調(diào)用一次,所以在發(fā)送第二個(gè)alloc消息就不會(huì)在調(diào)用了,他先輸出父類分類Eat的initialize打印方法(先調(diào)用分類的相同方法),然后輸出自己的initialize方法。OK這里initialize也講完了,最后我們?cè)賹?duì)比一下這兩個(gè)方法
load、initialize的區(qū)別
調(diào)用方式:load是根據(jù)函數(shù)地址直接調(diào)用,initialize是通過(guò)objc_msgSend調(diào)用
調(diào)用時(shí)刻:load是runtime加載類、分類的時(shí)候調(diào)用(只會(huì)調(diào)用1次),initialize是類第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類只會(huì)initialize一次(父類的initialize方法可能會(huì)被調(diào)用多次)
調(diào)用順序:先調(diào)用類的load方法,先編譯那個(gè)類,就先調(diào)用load。在調(diào)用load之前會(huì)先調(diào)用父類的load方法。分類中l(wèi)oad方法不會(huì)覆蓋本類的load方法,先編譯的分類優(yōu)先調(diào)用load方法。initialize先初始化父類,之后再初始化子類。如果子類沒(méi)有實(shí)現(xiàn)+initialize,會(huì)調(diào)用父類的+initialize(所以父類的+initialize可能會(huì)被調(diào)用多次),如果分類實(shí)現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用。
OK 到此category就講完了。