元類
- 實例對象是由類生產(chǎn)出來的凌彬,例如YYPerson這是一個類,通過YYPerson類我們可以調(diào)用其alloc方法循衰,創(chuàng)建出一個實例對象person铲敛,
YYPerson *person = [[YYPerson alloc]init]
,可以說類就像一個工廠一樣会钝, 工廠可以生產(chǎn)出成千上萬的產(chǎn)品伐蒋,這些產(chǎn)品的屬性和行為都是一樣,這些產(chǎn)品在編程的世界里就是所謂的實例對象迁酸,可以這么說生產(chǎn)實例對象的工廠我們稱之為類
先鱼,實例對象的isa指針指向生產(chǎn)實例對象的類
; - 正所謂在面向?qū)ο蟮木幊淌澜缋锛轺蓿f物皆對象焙畔,所以
生產(chǎn)實例對象的類,其本質(zhì)也是一個對象
全蝶,那么生產(chǎn)類對象的工廠我們稱之為元類
闹蒜,類對象的isa指針指向生產(chǎn)類對象的類寺枉,也就是所謂的元類
; - 元類是系統(tǒng)生成的绷落,其定義和創(chuàng)建都是由編譯器完成姥闪;
- 元類 是描述 類對象 的類,每個類都有一個獨一無二的
元類用來 存儲類方法的相關(guān)信息
砌烁; - 元類本身是沒有名稱的筐喳,由于與類相關(guān)聯(lián),所以使用了和類名一樣的名稱函喉;
int main(int argc, const char * argv[]) {
@autoreleasepool {
//實例對象
NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];
NSLog(@"實例對象 -- %p -- %p",obj1,obj2);
//類對象
Class objClass1 = [obj1 class];
Class objClass2 = [obj2 class];
Class objClass3 = object_getClass(obj1);
Class objClass4 = object_getClass(obj2);
Class objClass5 = [NSObject class];
NSLog(@"類對象 -- %p -- %p -- %p -- %p -- %p",objClass1,objClass2,objClass3,objClass4,objClass5);
//元類對象
Class metaClass = object_getClass(objClass1);
NSLog(@"元類對象 -- %p",metaClass);
//判斷類 是否是元類對象
bool isMetaClass = class_isMetaClass(metaClass);
NSLog(@"isMetaClass = %d",isMetaClass);
}
return 0;
}
- 調(diào)試結(jié)果如下:
- 可以看出避归,
不同的實例對象占據(jù)不同的內(nèi)存空間
,類對象在內(nèi)存中只占用一份內(nèi)存空間
管呵,元類對象在內(nèi)存中也只占用一份內(nèi)存空間
梳毙; -
object_getClass()
函數(shù),參數(shù)傳入實例對象捐下,返回的是類對象账锹; -
object_getClass()
函數(shù),參數(shù)傳入類對象坷襟,返回的是元類對象奸柬; -
class_isMetaClass()
函數(shù),判斷類 是否是元類對象婴程; - 下面我們通過代碼來驗證一下上面所闡述的內(nèi)容:
- YYPerson繼承自NSObject廓奕;
- YYStudent繼承自YYPerson;
- 當代碼執(zhí)行到斷點處停下档叔,進行LLDB命令調(diào)試桌粉,結(jié)果如下所示:
-
p/x person
讀取實例對象person在內(nèi)存中首地址; -
p/x 0x001d800100002375 & 0x00007ffffffffff8ULL
將實例對象person的isa的值與isa的掩碼0x00007ffffffffff8ULL做位與運算得到Y(jié)YPerson類的地址值0x0000000100002370
蹲蒲,其本質(zhì)就是YYPerson類對象番甩; -
x/4gx 0x0000000100002370
獲取YYPerson類對象在內(nèi)存中信息數(shù)據(jù);其中0x0000000100002348
就是YYPerson類對象的isa指針的值届搁; -
p/x 0x0000000100002348 & 0x00007ffffffffff8ULL
將YYPerson類對象的isa與isa的掩碼0x00007ffffffffff8ULL做位與運算得到Y(jié)YPerson元類的地址值,即0x0000000100002348
窍育,其本質(zhì)還是YYPerson卡睦,這就證實了上面所闡述的元類本身是沒有名稱的,由于與類相關(guān)聯(lián)漱抓,所以使用了同類名一樣的名稱表锻;
如何在LLDB調(diào)試控制臺獲取實例對象person的類對象YYPerson的內(nèi)存地址?
- 第一種方式:p/x [person class] 以16進制打印YYPerson類對象的內(nèi)存地址乞娄;
- 第二種方式:p/x [YYPerson class] 以16進制打印YYPerson類對象的內(nèi)存地址瞬逊;
- 第三種方式:p/x object_getClass(person) 以16進制打印YYPerson類對象的內(nèi)存地址显歧;
- 第四種方式:x/4gx person 首先讀取實例對象person在內(nèi)存中數(shù)據(jù),前8個字節(jié)是isa指針的值确镊,然后 p/x isa的值 & isa掩碼值士骤,得到的就是YYPerson類對象的內(nèi)存地址;
- p/x aaa 獲取的是aaa的內(nèi)存地址蕾域;
- x/4gx aaa 獲取的是aaa內(nèi)存地址中的數(shù)據(jù)內(nèi)容拷肌;
isa指針的指向
由上面的內(nèi)容我們知道實例對象的isa指向類對象
,類對象的isa指向元類對象
旨巷,那元類對象的isa指向哪里巨缘?會這樣沒有終點的一直指向下去么?
- 通過上面的LLDB調(diào)試分析可以得出下面的結(jié)論:
- 實例對象 的 isa 指向 類對象采呐;
- 類對象 的 isa 指向 其元類對象若锁;
- 元類對象 的 isa 指向 根元類,即NSObject斧吐;
- 根元類 的 isa 指向 它自己本身又固;
通過類Class所創(chuàng)建的實例對象在內(nèi)存中可以成千上萬,那么類對象在內(nèi)存中占幾份会通?
#import <Foundation/Foundation.h>
#import "YYPerson.h"
#import "YYStudent.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
Class class1 = [YYPerson class];
Class class2 = [YYPerson alloc].class;
Class class3 = object_getClass(person);
NSLog(@"\nclass1 = %p\nclass2 = %p\nclass3 = %p", class1, class2, class3);
}
return 0;
}
- 控制臺的打印結(jié)果如下:
- 可以看出
類對象在內(nèi)存中只占有一份
.
引用官方一張 關(guān)于 isa走向 與 類繼承關(guān)系圖
isa走向(虛線部分)
- 實例對象(Instance)的isa指向類對象class口予;
- 類對象的isa指向元類對象meta class;
- 元類對象的isa指向根元類 root meta class(NSObject)涕侈;
- 根元類對象的isa指向自己本身(NSObject)沪停;
類的繼承superClass的走向(實線部分)
- 類對象之間的繼承關(guān)系:
- 子類的SuperClass指向其父類Superclass;
- 父類的SuperClass指向根類RootClass裳涛,這里的根類就是NSOject木张;
- 根類的SuperClass指向nil,可以理解成無中生有端三;
- 元類對象之間也存在繼承關(guān)系:
- 子類的元類的SuperClass指向父類的元類Superclass(meta)舷礼;
- 父類的元類的SuperClass指向根元類RootClass(metal);
- 根元類的SuperClass指向根類RootClass 也就是NSObject郊闯;
- 根類的SuperClass指向nil妻献,可以理解成無中生有;
- 實例對象之間沒有繼承關(guān)系团赁,類與元類之間才有繼承關(guān)系育拨;
- 通過例子來實際闡述上面的關(guān)系圖,YYStudent與YYPerson:
- isa的走向鏈:
- student的走向鏈:student子類實例對象 --> YYStudent子類 --> YYStudent子類的元類 --> NSObject根元類 --> NSObject根元類自身
- Person的走向鏈:person子類實例對象 --> YYPerson子類 --> YYPerson子類的元類 --> NSObject根元類 --> NSObject根元類自身
- 類的繼承關(guān)系鏈:
- YYStudent子類 --> YYPerson父類 --> NSObject根類 --> nil
- YYStudent子類的元類 --> YYPerson父類的元類 --> NSObject根元類 --> NSObject根類 --> nil
- isa指針與superClass指針在方法調(diào)用中欢摄,起到至關(guān)重要的作用熬丧;
類的結(jié)構(gòu)
- 在 iOS底層系列02-- objc4-781源碼中的objc_class與objc_object中我們知道objc_class與objc_object這兩個結(jié)構(gòu)體且objc_class繼承自objc_object;
-
Class類
是以objc_class為模版進行創(chuàng)建的
怀挠; -
OC任意對象id
是以objc_object為模版進行創(chuàng)建的
析蝴; - objc_class結(jié)構(gòu)體定義(新的)如下所示:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
ASSERT(isFuture() || isRealized());
data()->setFlags(set);
}
......
}
-
isa指針
:繼承自objc_object害捕,占8個字節(jié); -
superclass指針
:屬于Class類型闷畸,是一個指針尝盼,占8個字節(jié); -
cache成員
:是一個cache_t結(jié)構(gòu)體
腾啥,其內(nèi)存大小需要根據(jù)其內(nèi)部的成員來確定东涡,詳細計算見下面; -
bits成員
:是一個class_data_bits_t
結(jié)構(gòu)體倘待,將Class的首地址進行偏移疮跑,偏移量為面3個成員的內(nèi)存大小總和,才能獲取到bits成員的首地址凸舵,bits成員存儲了類的相關(guān)信息數(shù)據(jù)祖娘;
計算cache成員的內(nèi)存大小
- 剔除不會占用類空間的const、void啊奄、static和函數(shù)渐苏,結(jié)構(gòu)體如下所示:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
- _buckets是結(jié)構(gòu)體指針類型占8個字節(jié);
- mask_t是unsigned int 占4個字節(jié)菇夸;
- uintptr_t是unsigned long 占8個字節(jié)琼富;
- uint16_t是unsigned short 占2個字節(jié);
- 所以cache_t結(jié)構(gòu)體不論哪種CACHE_MASK_STORAGE庄新,其
內(nèi)存大小都會占8+4+2+2 = 16個字節(jié)
鞠眉;
探索bits成員
- 根據(jù)上面關(guān)于cache內(nèi)存大小的計算結(jié)果,然后isa指針與superclass指針分別占8個字節(jié)择诈,再根據(jù)
內(nèi)存偏移
械蹋,我們需要將class類的首地址進行32字節(jié)的偏移
,方可得到bits成員的首地址羞芍; - YYPerson.h文件內(nèi)容:
@interface YYPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger weight;
- (void)walk;
- (void)eat;
+ (void)sing;
@end
- LLDB調(diào)試結(jié)果如下所示:
- p/x YYPerson.class 獲取
YYPerson類的首地址
- YYPerson類的首地址為0x100002360哗戈,那么
內(nèi)存偏移32個字節(jié)
即為0x100002380,是bits成員的首地址荷科,注意是16進制的換算
- p (class_data_bits_t *) 0x100002360 地址
強轉(zhuǎn)為class_data_bits_t類型
- p $1->data()唯咬,bits調(diào)用函數(shù)data(),
獲取class_rw_t結(jié)構(gòu)體
進入class_rw_t結(jié)構(gòu)體
- 在class_rw_t結(jié)構(gòu)體定義中看到三個函數(shù)分別是獲取
方法列表
畏浆,屬性列表
與協(xié)議列表
的副渴; -
method_array_t
,property_array_t
全度,protocol_array_t
均是一個二維數(shù)組; -
method_array_t
中存儲的是method_list_t
一維數(shù)組斥滤,method_list_t
中存儲的是method_t
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
- LLDB調(diào)試屬性列表properties()結(jié)果如下:
- p $3.properties() 調(diào)用class_rw_t結(jié)構(gòu)體中的properties()将鸵,獲取屬性列表數(shù)組property_list_t勉盅;
- 可通過p $6.get(0) 獲取屬性name;
- 可通過p $6.get(1) 獲取屬性weight顶掉;
- LLDB調(diào)試實例方法列表methods()結(jié)果如下:
- p $3.methods() 調(diào)用class_rw_t結(jié)構(gòu)體中的methods()草娜,獲取方法列表數(shù)組method_list_t,注意此方法列表是
實例方法列表
痒筒; - 可通過p $12.get(i)獲取對應的方法宰闰;
- LLDB調(diào)試實例變量的存儲:
- 首先在class_rw_t結(jié)構(gòu)體中存在下面這么一個函數(shù)ro(),其返回值為
class_ro_t結(jié)構(gòu)體
簿透;
const class_ro_t * ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;
}
return v.get<const class_ro_t *>();
}
- class_ro_t結(jié)構(gòu)體定義如下:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
......
};
- 有一個
ivars
成員移袍,此成員是用來存儲實例變量的; - ro中的方法列表老充,屬性列表與成員變量列表均是一維數(shù)組葡盗;
- p $5.ro() 獲取class_ro_t結(jié)構(gòu)體;
- p $7.ivars 獲取實例變量列表啡浊;
- p $9.get(0) 獲取實例變量 _name;
- p $9.get(1) 獲取實例變量 _weight;
- 總結(jié):
- 類的屬性列表存儲在類的bits屬性中觅够,可通過
bits --> data() --> properties()
獲取屬性列表; - 類的實例變量列表存儲在類的bits屬性中巷嚣,可通過
bits --> data() -->ro() --> ivars
獲取實例變量列表喘先; - 類的實例方法列表存儲在類的bits屬性中,可通過
bits --> data() --> methods()
獲取實例方法列表廷粒;
- 類的屬性列表存儲在類的bits屬性中觅够,可通過
探索類方法的存儲位置
- 類的bits成員窘拯,可通過data() --> methods()獲取的是類的實例方法,并沒有看到類方法评雌,猜測類方法應該存儲在元類的bits成員中树枫,下面通過LLDB來驗證一下:
- p/x YYPerson.class 獲取YYPerson類的首地址;
- x/4gx 0x00000001000023f0 讀取YYPerson類 前32個字節(jié)的內(nèi)存數(shù)據(jù)景东,其中0x00000001000023c8是isa的值砂轻,然后其與isa mask做位與運算得到元類的首地址0x00000001000023c8;
- 元類首地址偏移32個字節(jié)斤吐,得到元類的bits成員搔涝,
- 接下來的獲取類方法的步驟與獲取類的實例方法步驟相似;
- 總結(jié):
- 類的實例方法是存儲在類的bits成員中,
類 --> bits --> data() --> methods()
; - 類的類方法是存儲在元類的bits成員中,
元類 --> bits --> data() --> methods()
;
- 類的實例方法是存儲在類的bits成員中,
通過MJClassInfo.h查看類對象的數(shù)據(jù)結(jié)構(gòu)
- 上面是通過LLDB控制臺調(diào)試分析類對象的數(shù)據(jù)結(jié)構(gòu)和措,下面再提供一種更直觀的方式庄呈,查看類對象的數(shù)據(jù)結(jié)構(gòu),引用MJ大神寫的MJClassInfo.h文件派阱,代碼實現(xiàn)如下:
#import <Foundation/Foundation.h>
#ifndef MJClassInfo_h
#define MJClassInfo_h
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
struct cache_t {
bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
};
struct method_t {
SEL name;
const char *types;
IMP imp;
};
struct method_list_t : entsize_list_tt {
method_t first;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t alignment_raw;
uint32_t size;
};
struct ivar_list_t : entsize_list_tt {
ivar_t first;
};
struct property_t {
const char *name;
const char *attributes;
};
struct property_list_t : entsize_list_tt {
property_t first;
};
struct chained_property_list {
chained_property_list *next;
uint32_t count;
property_t list[0];
};
typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
uintptr_t count;
protocol_ref_t list[0];
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance對象占用的內(nèi)存空間
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 類名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t * methods; // 方法列表
property_list_t *properties; // 屬性列表
const protocol_list_t * protocols; // 協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
/* OC對象 */
struct mj_objc_object {
void *isa;
};
/* 類對象 */
struct mj_objc_class : mj_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
mj_objc_class* metaClass() {
return (mj_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* MJClassInfo_h */
- 工程測試代碼如下:
#import <Foundation/Foundation.h>
#import "YYPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "MJClassInfo.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
mj_objc_class *personClass = (__bridge mj_objc_class *)([YYPerson class]);
class_rw_t *personClassData = personClass->data();
class_rw_t *personMetaClassData = personClass->metaClass()->data();
}
return 0;
}
- 調(diào)試類對象YYPerson的結(jié)果如下:
- 可以看到實例變量與方法都是存儲在
class_ro_t
中诬留,class_rw_t
不存儲實例變量與方法,只提供訪問實例變量與方法的函數(shù),具體見class_rw_t的結(jié)構(gòu)體定義文兑; - 調(diào)試元類對象的結(jié)構(gòu)如下:
常見API
- 首先準備測試代碼YYPerson.h文件:
@interface YYPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger weight;
- (void)walk;
- (void)eat;
+ (void)sing;
@end
class_getInstanceMethod(Class cls, SEL sel)
-
class_getInstanceMethod(Class cls, SEL sel)
獲取類的實例方法盒刚,如果在傳入的類或者類的父類中沒有找到指定的實例方法,則返回NULL
绿贞;
void YYClass_getInstanceMethod(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(walk));
Method method2 = class_getInstanceMethod(metaClass, @selector(walk));
Method method3 = class_getInstanceMethod(pClass, @selector(sing));
Method method4 = class_getInstanceMethod(metaClass, @selector(sing));
NSLog(@"%s - %p - %p - %p - %p",__func__,method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
Class cls = object_getClass(person);
YYClass_getInstanceMethod(cls);
}
return 0;
}
- 測試代碼的結(jié)果分析:
1> method1 --> 0x100003270 有值 YYPerson類中有實例方法walk因块;
2> method2 --> 0x0 無值 YYPerson元類中沒有實例方法walk,其查找順序為: YYPerson元類 --> 根元類 --> 根類 --> nil籍铁,在元類的繼承鏈上查找
涡上;
3> method3 --> 0x0 無值 YYPerson類中沒有實例方法sing,其查找順序為YYPerson類 --> 根類 --> nil拒名,在類的繼承鏈上查找
吩愧;
4> method4 --> 0x100003208 有值 YYPerson元類中有實例方法sing;
5>元類中查找實例方法就是查找類方法
class_getClassMethod(Class cls, SEL sel)
-
class_getClassMethod(Class cls, SEL sel)
獲取類的類方法(元類的實例方法)靡狞,如果在傳入的類或者類的父類中沒有找到指定的類方法耻警,則返回NULL
;
其底層實現(xiàn)為:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
-
cls->getMeta()
獲取元類 表明類方法的查找是在元類中甸怕; -
若傳進來的class為元類甘穿,就直接返回元類;若傳進來的class為非元類梢杭,則會返回class的isa温兼,即class的元類
; - 測試代碼:
void YYclass_getClassMethod(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(walk));
Method method2 = class_getClassMethod(metaClass, @selector(walk));
Method method3 = class_getClassMethod(pClass, @selector(sing));
Method method4 = class_getClassMethod(metaClass, @selector(sing));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
Class cls = object_getClass(person);
YYclass_getClassMethod(cls);
}
return 0;
}
- 測試代碼的結(jié)果分析:
1> method1 --> 0x0 無值 傳參YYPerson為非元類武契,則獲取YYPerson的isa即YYPerson的元類募判,元類中沒有walk方法,且在元類的繼承鏈上查找咒唆,都沒有找到
届垫;
2> method2 --> 0x0 無值 傳參YYPerson為元類,而元類中沒有walk方法全释,且在元類的繼承鏈上查找装处,都沒有找到
;
3> method3 --> 0x1000031e0 有值 傳參YYPerson為非元類浸船,則獲取YYPerson的isa即YYPerson的元類妄迁,元類中有sing方法;
4> method4 --> 0x1000031e0 有值 傳參YYPerson為元類李命,元類中有sing方法登淘;
class_getMethodImplementation(Class cls, SEL sel)
-
class_getMethodImplementation(Class cls, SEL sel)
獲取類中某個方法的是實現(xiàn);
其底層實現(xiàn)為:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
- 若方法實現(xiàn)imp不存在封字,會進入消息的轉(zhuǎn)發(fā)黔州,也會返回一個函數(shù)指針耍鬓;
- 測試代碼如下:
void YYClass_getMethodImplementation(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(walk));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(walk));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sing));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sing));
NSLog(@"%p - %p - %p - %p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
Class cls = object_getClass(person);
YYClass_getMethodImplementation(cls);
}
return 0;
}
- 測試代碼的結(jié)果分析:
1> imp1 --> 0x100001c20 有值 YYPerson類有walk實例方法實現(xiàn),則返回walk實現(xiàn)的函數(shù)指針辩撑;
2> imp2 --> 0x10037eac0 有值 YYPerson元類中沒有walk實例方法(元類的實例方法也就是類方法)界斜,且在元類的繼承鏈上查找,都沒有找到
合冀;進入消息轉(zhuǎn)發(fā),返回消息轉(zhuǎn)發(fā)的函數(shù)指針项贺;
3> imp3 --> 0x10037eac0 有值 YYPerson類沒有sing實例方法君躺;在類的繼承鏈上查找,都沒有找到
开缎,進入消息轉(zhuǎn)發(fā)棕叫,返回消息轉(zhuǎn)發(fā)的函數(shù)指針;
4> imp4 --> 0x100001bb0 有值 YYPerson元類中有sing實例方法(即類方法)奕删,則返回sing實現(xiàn)的函數(shù)指針俺泣;
- (BOOL)isKindOfClass:(Class)cls與+ (BOOL)isKindOfClass:(Class)cls
-
- (BOOL)isKindOfClass:(Class)cls
判斷實例對象是否屬于指定參數(shù)類,會在實例對象的類的繼承鏈上判斷完残,其底層實現(xiàn)為:
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
可以看到首先獲取對象的類與傳參類進行比較伏钠,如果相等直接返回YES,
如果不相等會在類的繼承鏈上 父類 --> 根類 --> nil 循環(huán)與傳參類進行比較
谨设;+ (BOOL)isKindOfClass:(Class)cls
判斷類是否屬于指定參數(shù)類熟掂,會在類的元類的繼承鏈上判斷;
其底層實現(xiàn)為:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
可以看到首先獲取類的元類與傳參類進行比較扎拣,如果相等直接返回YES赴肚,
如果不相等會在元類的繼承鏈上 父類的元類 --> 根元類 --> 根類 --> nil 循環(huán)與傳參類進行比較
;測試代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
BOOL re1 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[YYPerson alloc] isKindOfClass:[YYPerson class]];
BOOL re3 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re4 = [(id)[YYPerson class] isKindOfClass:[YYPerson class]];
}
return 0;
}
- 發(fā)現(xiàn)一個問題isKindOfClass函數(shù)不論實例方法還是類方法都不會走上面的底層實現(xiàn)二蓝,分析其匯編代碼如下:
- isKindOfClass函數(shù)其實例方法與類方法誉券,底層調(diào)用的都是
objc_opt_isKindOfClass函數(shù)
,其代碼實現(xiàn)為:
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
- 若obj是實例對象刊愚,obj->getIsa()獲取的是類踊跟,若與傳參類一直不等,會在類的繼承鏈上依次進行比較百拓;
- 若obj是類琴锭,obj->getIsa()獲取的是元類,若與傳參類一直不等衙传,會在元類的繼承鏈上依次進行比較决帖;
- 底層這么調(diào)用,主要是因為在llvm中編譯時對其進行了優(yōu)化處理蓖捶。
- 上面測試代碼的結(jié)果分析:
- re1為YES地回,[NSObject alloc]實例對象的類為NSObject(根類)與傳參類NSObject(根類)相等;
- re2為YES,[YYPerson alloc]實例對象的類為YYPerson與傳參類YYPerson相等刻像;
- re3為YES畅买,[NSObject class]類的元類(根元類)與傳參類NSObject(根類)不相等,然后在元類的繼承鏈上细睡,父類的元類 --> 根元類 --> 根類 --> nil谷羞,依次比較,根元類的父類是根類NSObject與傳參類NSObject(根類)相等溜徙;
- re4為NO湃缎,[YYPerson class]類的元類與傳參類YYPerson(類)不相等,然后在元類的繼承鏈上蠢壹,父類的元類 --> 根元類 --> 根類 --> nil嗓违,依次比較,都不相等图贸;
- (BOOL)isMemberOfClass:(Class)cls與+ (BOOL)isMemberOfClass:(Class)cls
-
- (BOOL)isMemberOfClass:(Class)cls
判斷實例對象是否屬于指定參數(shù)類蹂季,不涉及類的繼承鏈,其底層實現(xiàn)為:
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- 可以看到僅僅只獲取實例對象的類與傳參類進行比較疏日;
-
+ (BOOL)isMemberOfClass:(Class)cls
判斷類是否屬于指定參數(shù)類偿洁,不涉及元類的繼承鏈;
其底層實現(xiàn)為:
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- 可以看到僅僅只獲取類的元類與傳參類進行比較制恍;
- 測試代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
BOOL re5 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re6 = [(id)[YYPerson alloc] isMemberOfClass:[YYPerson class]];
BOOL re7 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re8 = [(id)[YYPerson class] isMemberOfClass:[YYPerson class]];
}
return 0;
}
- isMemberOfClass函數(shù)調(diào)用的是上面的底層實現(xiàn)父能;與isKindOfClass函數(shù)底層調(diào)用不同;
- re5為YES净神,[NSObject alloc]的類為NSObject何吝,與傳參類NSObject相等;
- re6為YES鹃唯,[YYPerson alloc]的類為YYPerson爱榕,與傳參類YYPerson相等;
- re7為NO坡慌,[NSObject class]的元類即根元類與傳參類NSObject(根類)不相等黔酥;
- re8為NO,[YYPerson class]的元類與傳參類YYPerson(類)不相等洪橘;
如何獲取Class對象
- 首先Class對象包含兩種分別為:類對象和元類對象跪者;
-
- (Class)class 與 + (Class)class
:獲取的是類對象; -
objc_getClass("類字符串")
:獲取的是類對象熄求; -
object_getClass(id obj)
:參數(shù)傳入實例對象渣玲,返回類對象
,參數(shù)傳入類對象弟晚,返回元類對象
忘衍; - 代碼實現(xiàn)如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
//實例對象
NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];
NSLog(@"實例對象 -- %p -- %p",obj1,obj2);
//類對象
Class objClass1 = [obj1 class];
Class objClass2 = [obj2 class];
Class objClass3 = [NSObject class];
Class objClass4 = object_getClass(obj1);
Class objClass5 = objc_getClass("NSObject");
Class objClass6 = object_getClass(objClass1);
NSLog(@"class對象 -- %p -- %p -- %p -- %p -- %p -- %p",objClass1,objClass2,objClass3,objClass4,objClass5,objClass6);
NSLog(@"class對象 -- %p",objClass6);
}
return 0;
}
- 調(diào)試結(jié)果: