元類
- 實例對象是由類生產出來的,例如YYPerson這是一個類,通過YYPerson類我們可以調用其alloc方法,創建出一個實例對象person,
YYPerson *person = [[YYPerson alloc]init]
,可以說類就像一個工廠一樣, 工廠可以生產出成千上萬的產品,這些產品的屬性和行為都是一樣,這些產品在編程的世界里就是所謂的實例對象,可以這么說生產實例對象的工廠我們稱之為類
,實例對象的isa指針指向生產實例對象的類
; - 正所謂在面向對象的編程世界里,萬物皆對象,所以
生產實例對象的類,其本質也是一個對象
,那么生產類對象的工廠我們稱之為元類
,類對象的isa指針指向生產類對象的類,也就是所謂的元類
; - 元類是系統生成的,其定義和創建都是由編譯器完成;
- 元類 是描述 類對象 的類,每個類都有一個獨一無二的
元類用來 存儲類方法的相關信息
; - 元類本身是沒有名稱的,由于與類相關聯,所以使用了和類名一樣的名稱;
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;
}
- 調試結果如下:
Snip20210624_11.png
- 可以看出,
不同的實例對象占據不同的內存空間
,類對象在內存中只占用一份內存空間
,元類對象在內存中也只占用一份內存空間
; -
object_getClass()
函數,參數傳入實例對象,返回的是類對象; -
object_getClass()
函數,參數傳入類對象,返回的是元類對象; -
class_isMetaClass()
函數,判斷類 是否是元類對象; - 下面我們通過代碼來驗證一下上面所闡述的內容:
Snip20210210_137.png
- YYPerson繼承自NSObject;
- YYStudent繼承自YYPerson;
- 當代碼執行到斷點處停下,進行LLDB命令調試,結果如下所示:
Snip20210210_138.png
-
p/x person
讀取實例對象person在內存中首地址; -
p/x 0x001d800100002375 & 0x00007ffffffffff8ULL
將實例對象person的isa的值與isa的掩碼0x00007ffffffffff8ULL做位與運算得到YYPerson類的地址值0x0000000100002370
,其本質就是YYPerson類對象; -
x/4gx 0x0000000100002370
獲取YYPerson類對象在內存中信息數據;其中0x0000000100002348
就是YYPerson類對象的isa指針的值; -
p/x 0x0000000100002348 & 0x00007ffffffffff8ULL
將YYPerson類對象的isa與isa的掩碼0x00007ffffffffff8ULL做位與運算得到YYPerson元類的地址值,即0x0000000100002348
,其本質還是YYPerson,這就證實了上面所闡述的元類本身是沒有名稱的,由于與類相關聯,所以使用了同類名一樣的名稱;
如何在LLDB調試控制臺獲取實例對象person的類對象YYPerson的內存地址?
- 第一種方式:p/x [person class] 以16進制打印YYPerson類對象的內存地址;
- 第二種方式:p/x [YYPerson class] 以16進制打印YYPerson類對象的內存地址;
- 第三種方式:p/x object_getClass(person) 以16進制打印YYPerson類對象的內存地址;
- 第四種方式:x/4gx person 首先讀取實例對象person在內存中數據,前8個字節是isa指針的值,然后 p/x isa的值 & isa掩碼值,得到的就是YYPerson類對象的內存地址;
- p/x aaa 獲取的是aaa的內存地址;
- x/4gx aaa 獲取的是aaa內存地址中的數據內容;
isa指針的指向
由上面的內容我們知道實例對象的isa指向類對象
,類對象的isa指向元類對象
,那元類對象的isa指向哪里?會這樣沒有終點的一直指向下去么?
Snip20210210_140.png
- 通過上面的LLDB調試分析可以得出下面的結論:
- 實例對象 的 isa 指向 類對象;
- 類對象 的 isa 指向 其元類對象;
- 元類對象 的 isa 指向 根元類,即NSObject;
- 根元類 的 isa 指向 它自己本身;
通過類Class所創建的實例對象在內存中可以成千上萬,那么類對象在內存中占幾份?
#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;
}
- 控制臺的打印結果如下:
Snip20210210_142.png
- 可以看出
類對象在內存中只占有一份
.
引用官方一張 關于 isa走向 與 類繼承關系圖
Snip20210210_143.png
isa走向(虛線部分)
- 實例對象(Instance)的isa指向類對象class;
- 類對象的isa指向元類對象meta class;
- 元類對象的isa指向根元類 root meta class(NSObject);
- 根元類對象的isa指向自己本身(NSObject);
類的繼承superClass的走向(實線部分)
- 類對象之間的繼承關系:
- 子類的SuperClass指向其父類Superclass;
- 父類的SuperClass指向根類RootClass,這里的根類就是NSOject;
- 根類的SuperClass指向nil,可以理解成無中生有;
- 元類對象之間也存在繼承關系:
- 子類的元類的SuperClass指向父類的元類Superclass(meta);
- 父類的元類的SuperClass指向根元類RootClass(metal);
- 根元類的SuperClass指向根類RootClass 也就是NSObject;
- 根類的SuperClass指向nil,可以理解成無中生有;
- 實例對象之間沒有繼承關系,類與元類之間才有繼承關系;
- 通過例子來實際闡述上面的關系圖,YYStudent與YYPerson:
Snip20210213_5.png
- isa的走向鏈:
- student的走向鏈:student子類實例對象 --> YYStudent子類 --> YYStudent子類的元類 --> NSObject根元類 --> NSObject根元類自身
- Person的走向鏈:person子類實例對象 --> YYPerson子類 --> YYPerson子類的元類 --> NSObject根元類 --> NSObject根元類自身
- 類的繼承關系鏈:
- YYStudent子類 --> YYPerson父類 --> NSObject根類 --> nil
- YYStudent子類的元類 --> YYPerson父類的元類 --> NSObject根元類 --> NSObject根類 --> nil
- isa指針與superClass指針在方法調用中,起到至關重要的作用;
類的結構
- 在 iOS底層系列02-- objc4-781源碼中的objc_class與objc_object中我們知道objc_class與objc_object這兩個結構體且objc_class繼承自objc_object;
-
Class類
是以objc_class為模版進行創建的
; -
OC任意對象id
是以objc_object為模版進行創建的
; - objc_class結構體定義(新的)如下所示:
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個字節; -
superclass指針
:屬于Class類型,是一個指針,占8個字節; -
cache成員
:是一個cache_t結構體
,其內存大小需要根據其內部的成員來確定,詳細計算見下面; -
bits成員
:是一個class_data_bits_t
結構體,將Class的首地址進行偏移,偏移量為面3個成員的內存大小總和,才能獲取到bits成員的首地址,bits成員存儲了類的相關信息數據;
計算cache成員的內存大小
- 剔除不會占用類空間的const、void、static和函數,結構體如下所示:
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是結構體指針類型占8個字節;
- mask_t是unsigned int 占4個字節;
- uintptr_t是unsigned long 占8個字節;
- uint16_t是unsigned short 占2個字節;
- 所以cache_t結構體不論哪種CACHE_MASK_STORAGE,其
內存大小都會占8+4+2+2 = 16個字節
;
探索bits成員
- 根據上面關于cache內存大小的計算結果,然后isa指針與superclass指針分別占8個字節,再根據
內存偏移
,我們需要將class類的首地址進行32字節的偏移
,方可得到bits成員的首地址; - YYPerson.h文件內容:
@interface YYPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger weight;
- (void)walk;
- (void)eat;
+ (void)sing;
@end
- LLDB調試結果如下所示:
Snip20210219_2.png
- p/x YYPerson.class 獲取
YYPerson類的首地址
- YYPerson類的首地址為0x100002360,那么
內存偏移32個字節
即為0x100002380,是bits成員的首地址,注意是16進制的換算
- p (class_data_bits_t *) 0x100002360 地址
強轉為class_data_bits_t類型
- p $1->data(),bits調用函數data(),
獲取class_rw_t結構體
進入class_rw_t結構體
- 在class_rw_t結構體定義中看到三個函數分別是獲取
方法列表
,屬性列表
與協議列表
的; -
method_array_t
,property_array_t
,protocol_array_t
均是一個二維數組; -
method_array_t
中存儲的是method_list_t
一維數組,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調試屬性列表properties()結果如下:
Snip20210219_3.png
- p $3.properties() 調用class_rw_t結構體中的properties(),獲取屬性列表數組property_list_t;
- 可通過p $6.get(0) 獲取屬性name;
- 可通過p $6.get(1) 獲取屬性weight;
- LLDB調試實例方法列表methods()結果如下:
Snip20210219_4.png
- p $3.methods() 調用class_rw_t結構體中的methods(),獲取方法列表數組method_list_t,注意此方法列表是
實例方法列表
; - 可通過p $12.get(i)獲取對應的方法;
Snip20210219_5.png
- LLDB調試實例變量的存儲:
- 首先在class_rw_t結構體中存在下面這么一個函數ro(),其返回值為
class_ro_t結構體
;
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結構體定義如下:
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中的方法列表,屬性列表與成員變量列表均是一維數組;
Snip20210219_8.png
Snip20210219_9.png
Snip20210219_10.png
- p $5.ro() 獲取class_ro_t結構體;
- p $7.ivars 獲取實例變量列表;
- p $9.get(0) 獲取實例變量 _name;
- p $9.get(1) 獲取實例變量 _weight;
- 總結:
- 類的屬性列表存儲在類的bits屬性中,可通過
bits --> data() --> properties()
獲取屬性列表; - 類的實例變量列表存儲在類的bits屬性中,可通過
bits --> data() -->ro() --> ivars
獲取實例變量列表; - 類的實例方法列表存儲在類的bits屬性中,可通過
bits --> data() --> methods()
獲取實例方法列表;
- 類的屬性列表存儲在類的bits屬性中,可通過
探索類方法的存儲位置
- 類的bits成員,可通過data() --> methods()獲取的是類的實例方法,并沒有看到類方法,猜測類方法應該存儲在元類的bits成員中,下面通過LLDB來驗證一下:
Snip20210219_11.png
Snip20210219_12.png
- p/x YYPerson.class 獲取YYPerson類的首地址;
- x/4gx 0x00000001000023f0 讀取YYPerson類 前32個字節的內存數據,其中0x00000001000023c8是isa的值,然后其與isa mask做位與運算得到元類的首地址0x00000001000023c8;
- 元類首地址偏移32個字節,得到元類的bits成員,
- 接下來的獲取類方法的步驟與獲取類的實例方法步驟相似;
- 總結:
- 類的實例方法是存儲在類的bits成員中,
類 --> bits --> data() --> methods()
; - 類的類方法是存儲在元類的bits成員中,
元類 --> bits --> data() --> methods()
;
- 類的實例方法是存儲在類的bits成員中,
通過MJClassInfo.h查看類對象的數據結構
- 上面是通過LLDB控制臺調試分析類對象的數據結構,下面再提供一種更直觀的方式,查看類對象的數據結構,引用MJ大神寫的MJClassInfo.h文件,代碼實現如下:
#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對象占用的內存空間
#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; // 協議列表
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;
}
- 調試類對象YYPerson的結果如下:
Snip20210625_14.png
- 可以看到實例變量與方法都是存儲在
class_ro_t
中,class_rw_t
不存儲實例變量與方法,只提供訪問實例變量與方法的函數,具體見class_rw_t的結構體定義; - 調試元類對象的結構如下:
常見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;
}
- 測試代碼的結果分析:
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
;
其底層實現為:
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;
}
- 測試代碼的結果分析:
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)
獲取類中某個方法的是實現;
其底層實現為:
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;
}
- 若方法實現imp不存在,會進入消息的轉發,也會返回一個函數指針;
- 測試代碼如下:
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;
}
- 測試代碼的結果分析:
1> imp1 --> 0x100001c20 有值 YYPerson類有walk實例方法實現,則返回walk實現的函數指針;
2> imp2 --> 0x10037eac0 有值 YYPerson元類中沒有walk實例方法(元類的實例方法也就是類方法),且在元類的繼承鏈上查找,都沒有找到
;進入消息轉發,返回消息轉發的函數指針;
3> imp3 --> 0x10037eac0 有值 YYPerson類沒有sing實例方法;在類的繼承鏈上查找,都沒有找到
,進入消息轉發,返回消息轉發的函數指針;
4> imp4 --> 0x100001bb0 有值 YYPerson元類中有sing實例方法(即類方法),則返回sing實現的函數指針;
- (BOOL)isKindOfClass:(Class)cls與+ (BOOL)isKindOfClass:(Class)cls
-
- (BOOL)isKindOfClass:(Class)cls
判斷實例對象是否屬于指定參數類,會在實例對象的類的繼承鏈上判斷,其底層實現為:
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
可以看到首先獲取對象的類與傳參類進行比較,如果相等直接返回YES,
如果不相等會在類的繼承鏈上 父類 --> 根類 --> nil 循環與傳參類進行比較
;+ (BOOL)isKindOfClass:(Class)cls
判斷類是否屬于指定參數類,會在類的元類的繼承鏈上判斷;
其底層實現為:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
可以看到首先獲取類的元類與傳參類進行比較,如果相等直接返回YES,
如果不相等會在元類的繼承鏈上 父類的元類 --> 根元類 --> 根類 --> nil 循環與傳參類進行比較
;測試代碼如下:
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;
}
- 發現一個問題isKindOfClass函數不論實例方法還是類方法都不會走上面的底層實現,分析其匯編代碼如下:
Snip20210220_16.png
- isKindOfClass函數其實例方法與類方法,底層調用的都是
objc_opt_isKindOfClass函數
,其代碼實現為:
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()獲取的是元類,若與傳參類一直不等,會在元類的繼承鏈上依次進行比較;
- 底層這么調用,主要是因為在llvm中編譯時對其進行了優化處理。
- 上面測試代碼的結果分析:
- 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
判斷實例對象是否屬于指定參數類,不涉及類的繼承鏈,其底層實現為:
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- 可以看到僅僅只獲取實例對象的類與傳參類進行比較;
-
+ (BOOL)isMemberOfClass:(Class)cls
判斷類是否屬于指定參數類,不涉及元類的繼承鏈;
其底層實現為:
+ (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函數調用的是上面的底層實現;與isKindOfClass函數底層調用不同;
- 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)
:參數傳入實例對象,返回類對象
,參數傳入類對象,返回元類對象
; - 代碼實現如下:
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;
}
- 調試結果:
Snip20210624_12.png