前言
當(dāng)一個相同的類創(chuàng)建多個對象時,那么這么對象的類是不是創(chuàng)建了多個呢
Class d1 = [Desk class];
Class d2 = [Desk alloc].class;
Class d3 = object_getClass([Desk alloc]);
Class d4 = [Desk alloc].class;
NSLog(@"%p - %p - %p - %p", d1, d2, d3, d4);
--------------
Room[42703:2055484] 0x1000080f0 - 0x1000080f0 - 0x1000080f0 - 0x1000080f0
類對象地址顯然相同劲藐, 所以 類在內(nèi)存空間中只存了一份
回顧
上篇我們知道類在底層一個繼承于 objc_object 的對象,也就是object_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);
}
}
我們知道 objc_class
第一個內(nèi)存是 isa
,superclass
存儲的父類信息奴饮,占據(jù)8個字節(jié),cache
緩存占用 16 字節(jié)择浊, bits
存儲了類的方法拐云,屬性,協(xié)議等信息的地方近她,具體的注釋表示 class_data_bits_t
相當(dāng)于 class_rw_t 指針加上 rr/alloc 的標(biāo)識
class_data_bits_t
結(jié)構(gòu)體返回了 class_rw_t
指針,那 class_rw_t
又是什么呢?
就在代碼旁簡單謝謝介紹
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
// 當(dāng)前所屬類 第一個子類
Class firstSubclass;
// 當(dāng)前所有類 的兄弟類
Class nextSiblingClass;
private:
// ro_or_rw_ext_t會有兩種情況:
// 1): 值是 class_ro_t *
// 2): 值是 class_rw_ext_t *叉瘩,
// 而 class_ro_t * 作為 class_rw_ext_t 的 const class_ro_t *ro 成員變量保存
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
... 此處省略
}
// class_rw_ext_t 其中 rw 可以理解為 read write
struct class_rw_ext_t {
// 存放只讀類型
const class_ro_t *ro;
// 方法列表
method_array_t methods;
// 屬性列表
property_array_t properties;
// 協(xié)議列表
protocol_array_t protocols;
// 所屬類名
char *demangledName;
// 版本號
uint32_t version;
};
// class_ro_t 其中 ro 可以理解為 read only
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;
... 此處省略
};
可以看出 class_rw_t
存放者類的屬性 協(xié)議 方法等信息
現(xiàn)在我們驗證一下類的成員變量 屬性存儲的位置
準(zhǔn)備
@interface TObject : NSObject
{
NSString *tname;
}
@property (nonatomic, copy) NSString* toName;
+ (void) classMethodTest;
- (void) instanceMethodTest;
@end
@implementation TObject
+(void)classMethodTest{
NSLog(@"%s",__func__);
}
-(void)instanceMethodTest{
NSLog(@"%s",__func__);
}
@end
成員變量 / 屬性 存儲的位置
接下我 我們就要利用 lldb
調(diào)試 一步一步驗證
- 我們先要獲取類對象的內(nèi)存地址,通過類結(jié)構(gòu)分析
class_data_bits_t bits
字段之前 還有32 字節(jié)
所以我們需要通過內(nèi)存便宜獲取bits
的內(nèi)存地址
在這我們介紹一個命令x/5gx
其中第一個 x 表示讀取內(nèi)存粘捎,5表示分成5段薇缅,g 表示一個單元8個字節(jié)危彩,x 表示16進(jìn)制, 所以其中第5個地址 就是 bits 的地址
(lldb) p obj1.class
(Class) $0 = TObject
(lldb) x/5gx $0
0x1000021f0: 0x00000001000021c8 0x0000000100333140
0x100002200: 0x00000001018335c0 0x0001802400000007
0x100002210: 0x00000001006656a4
(lldb)
- 我們現(xiàn)在需要將 地址強(qiáng)轉(zhuǎn)為
class_data_bits_t
類型,并通過 data() 函數(shù) 獲取class_rw_t
(lldb) p (class_data_bits_t*)0x100002210
(class_data_bits_t *) $1 = 0x0000000100002210
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000020a000000000
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975648
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
- 此時我們調(diào)用class_rw_t 結(jié)構(gòu)體中的 properties() 獲取屬性列表,可以看到屬性名稱 toName
(lldb) p $3.properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002198
arrayAndFlag = 4294975896
}
}
}
(lldb) p $4.list
(property_list_t *const) $5 = 0x0000000100002198
p *$5
(property_list_t) $6 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "toName", attributes = "T@\"NSString\",C,N,V_toName") // 其中 C 代Copy泳桦,N 代表 nonatomic
}
}
- 此時 我們的成員變量又去哪了呢汤徽,接下來 我們接著調(diào)用 class_rw_t 的 ro屬性,并獲取 ro 的 ivar
(lldb) p $3.ro()
(const class_ro_t *) $9 = 0x00000001000020a0
(lldb) p $9->ivars
(const ivar_list_t *const) $10 = 0x0000000100002150
(lldb) p *$10
(const ivar_list_t) $12 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000021c0 // 偏移量
name = 0x0000000100000eb9 "tname" // 名稱
type = 0x0000000100000f8a "@\"NSString\"" // 類型
alignment_raw = 3 // 8字節(jié)對齊
size = 8 // 內(nèi)存占用大小
}
}
}
由此可見 我們的 屬性
是存在 class_rw_t
中的 屬性列表
中的,而成員變量
是存儲在 class_ro_t
中的 ivars
,成員變量列表中的
實例方法 / 類方法 的存儲位置
- 接著上面的調(diào)用灸撰,我們先獲取
class_rw_t
的方法列表methods()
,在讀取 列表信息
(lldb) p $3.methods()
(const method_array_t) $7 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020e0
arrayAndFlag = 4294975712
}
}
}
(lldb) p $7.list
(method_list_t *const) $8 = 0x00000001000020e0
(lldb) p *$8
(method_list_t) $9 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 3
first = {
name = "instanceMethodTest"
types = 0x0000000100000f34 "v16@0:8"
imp = 0x0000000100000db0 (Objc_alloc`-[TObject instanceMethodTest])
}
}
}
(lldb)
由此可見我們的實例方法
確實是存儲在 class_rw_t
中的方法列表中的
- 現(xiàn)在找找我們的類方法存到哪了谒府, 先看看
class_ro_t
中的baseMethodList
lldb) p $3.ro()
(const class_ro_t *) $10 = 0x0000000100002098
(lldb) p $10->baseMethodList
(method_list_t *const) $12 = 0x00000001000020e0
(lldb) p *$12
(method_list_t) $13 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 3
first = {
name = "instanceMethodTest"
types = 0x0000000100000f34 "v16@0:8"
imp = 0x0000000100000db0 (Objc_alloc`-[TObject instanceMethodTest])
}
}
}
(lldb)
我們看到 在 class_ro_t
中的方法列表中,還是存儲著我們的實例方法信息
那我們的類方法到底去哪了呢浮毯,通過 isa
的繼承關(guān)系 推導(dǎo)完疫,會不會在我們的元類中呢
- 現(xiàn)在我們開始獲取元類地址,然后獲取元類的
class_rw_t
中的方法列表中债蓝,流程和上面的是一樣的
(lldb) p objc_getMetaClass(object_getClassName(obj1))
(Class) $14 = 0x00000001000021a8
(lldb) x/5gx $14
0x1000021a8: 0x00000001003330f0 0x00000001003330f0
0x1000021b8: 0x0000000101925020 0x0001e03500000007
0x1000021c8: 0x00000001006ce2e4
(lldb) p (class_data_bits_t*)0x1000021c8
(class_data_bits_t *) $15 = 0x00000001000021c8
(lldb) p $15->data()
(class_rw_t *) $17 = 0x00000001006ce2e0
(lldb) p *$17
(class_rw_t) $18 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975536
}
firstSubclass = nil
nextSiblingClass = 0x00007fff89e08cf8
}
(lldb) p $18.methods
(const method_array_t) $19 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002078
arrayAndFlag = 4294975608
}
}
}
Fix-it applied, fixed expression was:
$18.methods()
(lldb) p $19.list
(method_list_t *const) $20 = 0x0000000100002078
(lldb) p *$20
(method_list_t) $21 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "classMethodTest"
types = 0x0000000100000f34 "v16@0:8"
imp = 0x0000000100000d80 (Objc_alloc`+[TObject classMethodTest])
}
}
}
(lldb)
由此可見 我們的類方法
是存儲在對象所屬類的元類
當(dāng)中的方法列表
中
總結(jié)
這是一張類的結(jié)構(gòu)圖
- 方法
-
實例方法
確實是存儲在class_rw_t
中的methods
中的 -
類方法
存儲在 當(dāng)前對象所屬類的元類
中的class_rw_t
中的methods
中
-
- 屬性
- 對象屬性 ->
class_rw_t
中的properties
中 - 成員變量 ->
class_rw_t
->class_ro_t *ro
->ivars
- 對象屬性 ->
補(bǔ)充 - 驗證以上
- 例1 通過
class_getInstanceMethod
獲取類的實例方法
Class class = TObject.class;
const char *className = class_getName(class);
Class metaClass = objc_getMetaClass(className);
// --- class_getInstanceMethod
Method method1 = class_getInstanceMethod(class, @selector(instanceMethodTest));
Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethodTest));
Method method3 = class_getInstanceMethod(class, @selector(classMethodTest));
Method method4 = class_getInstanceMethod(metaClass, @selector(classMethodTest));
NSLog(@"class_getInstanceMethod - %p-%p-%p-%p",method1,method2,method3,method4);
===========
TObjc[4080:382660] class_getInstanceMethod - 0x100003d51-0x0-0x0-0x100003d39
class_getInstanceMethod
返回的是類的實例方法壳鹤,如果類或者父類中沒有找到,就返回nil
/***********************************************************************
* class_getInstanceMethod. Return the instance method for the
* specified class and selector.
**********************************************************************/
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.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
以上 method1 和 method4 是有值的饰迹,進(jìn)一步驗證了 實例方法存在類中芳誓,類方法存在元類中,
也給了我們另一個定義 也就是說 類方法啊鸭,對于元類來說 其實就是一個實例方法
- 例2 獲取 類方法
class_getClassMethod
// --- class_getClassMethod
Method method5 = class_getClassMethod(class, @selector(instanceMethodTest));
Method method6 = class_getClassMethod(metaClass, @selector(instanceMethodTest));
Method method7 = class_getClassMethod(class, @selector(classMethodTest));
Method method8 = class_getClassMethod(metaClass, @selector(classMethodTest));
NSLog(@"class_getClassMethod - %p-%p-%p-%p",method5,method6,method7,method8);
========
2020-12-15 21:54:56.966095+0800 TObjc[4080:382660] class_getClassMethod - 0x0-0x0-0x100003d39-0x100003d39
class_getClassMethod
這個方法 其實就是使用元類 獲取實例方法
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();
}
其實都是走的class_getInstanceMethod
函數(shù)锹淌,method7 之所以會有值,也是因為 getMeta()
這個函數(shù)返回元類的原因赠制, 然而當(dāng) 傳入的是元類的話 就會返回本身葛圃,這也就意味著 method7
和method8
其實是一樣的
3.例3 獲取方法實現(xiàn)class_getMethodImplementation
// 尋找方法實現(xiàn) class_getMethodImplementation
IMP imp1 = class_getMethodImplementation(class, @selector(instanceMethodTest));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(instanceMethodTest));
IMP imp3 = class_getMethodImplementation(class, @selector(classMethodTest));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethodTest));
NSLog(@" class_getMethodImplementation %p-%p-%p-%p",imp1,imp2,imp3,imp4);
=======
2020-12-15 21:54:56.966114+0800 KCObjc[4080:382660] class_getMethodImplementation 0x100003ab4-0x199ce6600-0x199ce6600-0x100003a7c
其中有一個很重要的東西 _objc_msgForward
消息轉(zhuǎn)發(fā)
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;
}
所以 imp2
和 imp3
是沒有找到的,只是進(jìn)行了消息轉(zhuǎn)發(fā)憎妙,所以打印都是有值的
class_getInstanceMethod
:返回的是類的實例方法,如果在傳入的類或者類的父類中沒有找到指定的實例方法曲楚,則返回空厘唾。
class_getClassMethod
:獲取類方法,本質(zhì)上還是調(diào)用 class_getInstanceMethod
,不過在這其中的參數(shù) 變成了元類龙誊,如果傳入的類或者父類中 對應(yīng)的元類中沒有找到抚垃,那就返回空
class_getMethodImplementation
: 尋找方法實現(xiàn),如果在對應(yīng)的類拐邪,或者元類中沒有找到蜒谤,那就進(jìn)行消息轉(zhuǎn)發(fā)