前言
書接上回isa結(jié)構(gòu)分析,我們得知,對(duì)象的isa指針指向類,確切的說,是isa指針的shiftcls
位域中指向類對(duì)象Class
的地址。那OC層的Class
在底層C、C++層面里,所對(duì)應(yīng)的結(jié)構(gòu)體里,有哪些成員變量和函數(shù)呢?下面將大致分析下類的底層結(jié)構(gòu)。
isa指向
首先我們來看看isa指針的指向問題,我們都知道,對(duì)象的isa指針
指向類對(duì)象Class,那類對(duì)象Class的isa指針
指向哪里呢?我們先通過在lldb中打印地址來查看詳情。
我們先定義一個(gè)類LGPerson
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@end
NS_ASSUME_NONNULL_END
//--------------------分割線----------------------
#import "LGPerson.h"
@implementation LGPerson
@end
在程序入口main.m
中生成LGperson
對(duì)象
#import <Foundation/Foundation.h>
#import "LGPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGPerson *person = [[LGPerson alloc] init];
}
return 0;
}
打斷點(diǎn)查看person對(duì)象的地址信息
(lldb) po person
<LGPerson: 0x10124dcb0>
(lldb) p person
(LGPerson *) $2 = 0x000000010124dcb0
我們常用的LLDB
指令po,p
,他倆的區(qū)別如下:
指令名稱 | 釋義 |
---|---|
p | 是 expression 的別名,打印某個(gè)東西,可以是變量和表達(dá)式 |
po | 一般用于打印對(duì)象,是 expression -O 的別名,會(huì)輸出對(duì)應(yīng)的值 |
再來看看另外兩個(gè)LLDB指令
指令名稱 | 釋義 |
---|---|
p/x | 以16進(jìn)制形式讀取對(duì)象的地址或者值 |
x/4gx | 以16進(jìn)制形式讀取4段8位的內(nèi)存空間里面存儲(chǔ)的地址或者值 |
用上面指令看看person對(duì)象
的地址
(lldb) p/x person
(LGPerson *) $4 = 0x000000010124dcb0
(lldb) x/4gx person
0x10124dcb0: 0x001d8001000020e9 0x0000000000000000
0x10124dcc0: 0x0000000080080000 0x00007fff8d07d208
首先第一段0x10124dcb0: 0x001d8001000020e9
這個(gè)就是person
對(duì)象的isa指針
地址,再查看isa指針
中位域shiftcls
的值,有個(gè)小技巧,通過和ISA_MASK
進(jìn)行與運(yùn)算
,過濾出shiftcls
的地址值
在查看isa_t結(jié)構(gòu)體中,可以找到ISA_MASK的宏定義(以x86_64為例)
define ISA_MASK 0x00007ffffffffff8ULL
(lldb) p/x 0x001d8001000020e9 & 0x00007ffffffffff8ULL
(unsigned long long) $6 = 0x00000001000020e8
(lldb) po $6
LGPerson
驗(yàn)證得出:person對(duì)象
的isa指針
的位域shiftcls
中指向的是類LGPerson
的地址。
同理,我們?cè)诶^續(xù)看看類LGPerson
對(duì)象的isa指針位域shiftcls
中存放的什么值?
// 以4段方式查看LGPeson類對(duì)象地址
(lldb) x/4gx $6
0x1000020e8: 0x00000001000020c0 0x00000001003ef190
0x1000020f8: 0x0000000101063830 0x0004801000000007
// 首地址isa & ISA_MASK,得到位域shiftcls中存放的值
(lldb) p/x 0x00000001000020c0 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x00000001000020c0
// 打印shiftcls值
(lldb) po $7
LGPerson
發(fā)現(xiàn),類對(duì)象LGPerson
的isa指針中shiftcls
存儲(chǔ)的值也是LGPerson
,但是$6 = 0x00000001000020e8
與$7 = 0x00000001000020c0
的地址值顯然不同,說明$7
明顯不是指向類對(duì)象LGPerson
它自己,但是打印出的值是LGPerson
,這就是蘋果提出的一個(gè)概念元類(meta class)
。
驗(yàn)證得出,類對(duì)象的isa指向與對(duì)象的isa指向不同,對(duì)象的isa指向類,而類的isa指向元類(蘋果爸爸定義為meta class)。
接著我們繼續(xù)查看$7
的isa指針的位域shiftcls
中存放的地址值是什么?
(lldb) x/4gx $7
0x1000020c0: 0x00000001003ef140 0x00000001003ef140
0x1000020d0: 0x0000000101063b40 0x0003e03100000007
(lldb) p/x 0x00000001003ef140 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x00000001003ef140
(lldb) po $8
NSObject
$7
指針的位域shiftcls
中存放的地址$8
值是NSObject
。接著看$8
的shiftcls
的值,如下:
(lldb) x/4gx $8
0x1003ef140: 0x00000001003ef140 0x00000001003ef190
0x1003ef150: 0x0000000100640ff0 0x0004e03100000007
(lldb) p/x 0x00000001003ef140 & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x00000001003ef140
(lldb) po $9
NSObject
$8 = 0x00000001003ef140
與 0x1003ef140: 0x00000001003ef140
(isa指針地址)一樣,shiftcls
打印出的地址也是0x00000001003ef140
,值也是NSObject,說明$8
的isa指針
指向自己。
那這個(gè)$9
的值NSObject
,是不是我們通常的基類NSObject類呢?接下來驗(yàn)證一下
(lldb) p/x NSObject.class
(Class) $10 = 0x00000001003ef190 NSObject
$10
的地址與$9
不同,說明不是基類。
驗(yàn)證得出,元類的isa指針指向NSObject根元類(蘋果爸爸定義為root meta class)。
上一張isa指向
經(jīng)典圖:
以上通過LLDB
中的指令查看地址,得出isa
指針的指向是:
對(duì)象 --> 類 --> 元類 --> 根元類-->根元類 指向自身
。
特殊問題:類信息在內(nèi)存中存在幾份
以上我們得知,類對(duì)象的isa指針指向元類,那類對(duì)象Class的isa指針指向的地址是同一個(gè)還是不同的?我們還是用代碼驗(yàn)證一下:
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
NSLog(@"\n%p\n%p\n%p", class1, class2, class3);
編譯運(yùn)行后得出:
2020-09-14 17:23:07.926664+0800 KCObjc[9752:305959]
0x100002100
0x100002100
0x100002100
地址一模一樣,說明類信息在內(nèi)存中只存在一份。
類Class結(jié)構(gòu)分析
言歸正傳,分析了isa指針指向問題后,現(xiàn)在再來回頭看看Class結(jié)構(gòu)的具體情況:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
Class
對(duì)應(yīng)底層結(jié)構(gòu)體是objc_class
,再來看看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();
}
// 省略部分代碼.......
}
-
objc_class
繼承objc_object
-
superclass
是父類Class -
cache_t cache
緩存 -
class_data_bits_t bits
成員
cache_t結(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;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
}
// 省略部分代碼...
CACHE_MASK_STORAGE_OUTLINED
這種緩存模式,則占用內(nèi)存大小是:bucket_t
+mask_t
+uint16_t
+uint16_t
CACHE_MASK_STORAGE
這種緩存模式,則占用內(nèi)存大小是:uintptr_t
+mask_t
+uint16_t
+uint16_t
uintptr_t內(nèi)存大小
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
uintptr_t
是long類型,8個(gè)字節(jié)
bucket_t內(nèi)存大小
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
// 省略部分代碼...
}
不管是__arm64__
還是其它,都是uintptr_t
+_sel
指針 --> 4 + 8 = 12
mask_t內(nèi)存大小
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
一目了然,即整型,大小為4。
綜上所述,
-
CACHE_MASK_STORAGE_OUTLINED
模式下,explicit_atomic<struct bucket_t *> _buckets
是結(jié)構(gòu)體bucket_t
的指針 8,+mask_t
4 + 2 *uint16_t
2 = 16 -
CACHE_MASK_STORAGE
模式下是8+4+2+2,也是16;
結(jié)論cache_t 所占內(nèi)存大小:16
重點(diǎn) class_data_bits_t
結(jié)構(gòu)體class_data_bits_t
大致分為幾個(gè)部分:
-
bits
操作相關(guān),為私有函數(shù),包含獲取、設(shè)置、清空等操作
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
// Atomically set the bits in `set` and clear the bits in `clear`.
// set and clear must not overlap.
void setAndClearBits(uintptr_t set, uintptr_t clear)
{
// ...省略部分代碼
}
void setBits(uintptr_t set) {
__c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED);
}
void clearBits(uintptr_t clear) {
__c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED);
}
2.重要部分data()
,返回結(jié)構(gòu)體class_rw_t *
指針類型,里面存放了屬性列表,實(shí)例方法列表,協(xié)議列表等信息
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData) {
// ...省略部分代碼
}
3.ro data
相關(guān)
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() {
// ...省略部分代碼
}
void setClassArrayIndex(unsigned Idx) {
#if SUPPORT_INDEXED_ISA
// 0 is unused as then we can rely on zero-initialisation from calloc.
ASSERT(Idx > 0);
data()->index = Idx;
#endif
}
unsigned classArrayIndex() {
#if SUPPORT_INDEXED_ISA
return data()->index;
#else
return 0;
#endif
}
4.swift相關(guān)
bool isAnySwift() {
return isSwiftStable() || isSwiftLegacy();
}
bool isSwiftStable() {
return getBit(FAST_IS_SWIFT_STABLE);
}
void setIsSwiftStable() {
setAndClearBits(FAST_IS_SWIFT_STABLE, FAST_IS_SWIFT_LEGACY);
}
bool isSwiftLegacy() {
return getBit(FAST_IS_SWIFT_LEGACY);
}
void setIsSwiftLegacy() {
setAndClearBits(FAST_IS_SWIFT_LEGACY, FAST_IS_SWIFT_STABLE);
}
// fixme remove this once the Swift runtime uses the stable bits
bool isSwiftStable_ButAllowLegacyForNow() {
return isAnySwift();
}
_objc_swiftMetadataInitializer swiftMetadataInitializer() {
// This function is called on un-realized classes without
// holding any locks.
// Beware of races with other realizers.
return safe_ro()->swiftMetadataInitializer();
}
class_rw_t內(nèi)部信息
class_rw_t
里存放了類的實(shí)例方法列表,屬性列表,協(xié)議列表等信息,如何查看呢?下面舉例說明。
首先在LGPerson
類中添加屬性和方法
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) NSString *nikeName;
- (void)sayHello;
+ (void)sayByebye;
@end
查看LGPerson
類的地址
(lldb) p/x LGPerson.class
(Class) $0 = 0x00000001000021f8
// 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
根據(jù)上面objc_class
的結(jié)構(gòu)分析,要獲取class_data_bits_t bits
的地址,需要位移isa
8位+Class
8位 + cache_t
16位 = 32位
0x00000001000021f8 + 0x20 = 0x0000000100002218
獲取class_data_bits_t
(lldb) p (class_data_bits_t *)0x0000000100002218
(class_data_bits_t *) $1 = 0x0000000100002218
獲取data()
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000101142b80
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975608
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
1. 查看屬性列表信息
(lldb) p $3.properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002188
arrayAndFlag = 4294975880
}
}
}
(lldb) p $4.list
(property_list_t *const) $5 = 0x0000000100002188
(lldb) p *$5
(property_list_t) $6 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 2
first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
}
}
count = 2
說明有2個(gè)屬性,再仔細(xì)查看這兩個(gè)屬性:
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $6.get(1)
(property_t) $8 = (name = "nikeName", attributes = "T@\"NSString\",C,N,V_nikeName")
(lldb) p $6.get(2)
Assertion failed: (i < count), function get, file /Users/Aron/objc4_debug/objc4-781/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
name
nikeName
都有了,當(dāng)獲取第三個(gè)時(shí)越界,Assertion failed: (i < count)
表示下標(biāo)必須小于count:2
2. 查看方法列表信息
同理,查看方法列表methods()
(lldb) p $3.methods()
(const method_array_t) $9 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020c0
arrayAndFlag = 4294975680
}
}
}
(lldb) p $9.list
(method_list_t *const) $10 = 0x00000001000020c0
(lldb) p *$10
(method_list_t) $11 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 5
first = {
name = "nikeName"
types = 0x0000000100000f98 "@16@0:8"
imp = 0x0000000100000e20 (KCObjc`-[LGPerson nikeName])
}
}
}
count = 5
說明有5個(gè)方法,明明我只定義了2個(gè)方法,- (void)sayHello;
和+ (void)sayByebye;
,再一一打印看看是哪5個(gè)方法:
(lldb) p $11.get(0)
(method_t) $12 = {
name = "nikeName"
types = 0x0000000100000f98 "@16@0:8"
imp = 0x0000000100000e20 (KCObjc`-[LGPerson nikeName])
}
(lldb) p $11.get(1)
(method_t) $13 = {
name = "setNikeName:"
types = 0x0000000100000fa0 "v24@0:8@16"
imp = 0x0000000100000e50 (KCObjc`-[LGPerson setNikeName:])
}
(lldb) p $11.get(2)
(method_t) $14 = {
name = ".cxx_destruct"
types = 0x0000000100000f90 "v16@0:8"
imp = 0x0000000100000d90 (KCObjc`-[LGPerson .cxx_destruct])
}
(lldb) p $11.get(3)
(method_t) $15 = {
name = "name"
types = 0x0000000100000f98 "@16@0:8"
imp = 0x0000000100000dd0 (KCObjc`-[LGPerson name])
}
(lldb) p $11.get(4)
(method_t) $16 = {
name = "setName:"
types = 0x0000000100000fa0 "v24@0:8@16"
imp = 0x0000000100000df0 (KCObjc`-[LGPerson setName:])
}
原來是2個(gè)屬性的get set
方法和一個(gè)析構(gòu)函數(shù)cxx_destruct
,那定義的2個(gè)方法去哪了?
補(bǔ)坑:
原來之前只定義了方法,并未實(shí)現(xiàn)sayHello
sayByebye
這2個(gè)方法。。。
先補(bǔ)上方法實(shí)現(xiàn),重新編譯
@implementation LGPerson
- (void)sayHello {
}
+ (void)sayByebye {
}
@end
再打印方法列表看看:
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 6
first = {
name = "sayHello"
types = 0x0000000100000f35 "v16@0:8"
imp = 0x0000000100000d60 (KCObjc`-[LGPerson sayHello])
}
}
}
發(fā)現(xiàn)count = 6,不是7個(gè)
,看看少了哪個(gè)?
(lldb) p $6.get(0)
(method_t) $7 = {
name = "sayHello"
types = 0x0000000100000f35 "v16@0:8"
imp = 0x0000000100000d60 (KCObjc`-[LGPerson sayHello])
}
(lldb) p $6.get(1)
(method_t) $8 = {
name = "nikeName"
types = 0x0000000100000f49 "@16@0:8"
imp = 0x0000000100000e00 (KCObjc`-[LGPerson nikeName])
}
(lldb) p $6.get(2)
(method_t) $9 = {
name = "setNikeName:"
types = 0x0000000100000f51 "v24@0:8@16"
imp = 0x0000000100000e30 (KCObjc`-[LGPerson setNikeName:])
}
(lldb) p $6.get(3)
(method_t) $10 = {
name = ".cxx_destruct"
types = 0x0000000100000f35 "v16@0:8"
imp = 0x0000000100000d70 (KCObjc`-[LGPerson .cxx_destruct])
}
(lldb) p $6.get(4)
(method_t) $11 = {
name = "name"
types = 0x0000000100000f49 "@16@0:8"
imp = 0x0000000100000db0 (KCObjc`-[LGPerson name])
}
(lldb) p $6.get(5)
(method_t) $12 = {
name = "setName:"
types = 0x0000000100000f51 "v24@0:8@16"
imp = 0x0000000100000dd0 (KCObjc`-[LGPerson setName:])
}
發(fā)現(xiàn)少了sayByebye
,這是類方法,根據(jù)isa指針走位分析,實(shí)例方法存儲(chǔ)在類中,那類方法應(yīng)該存儲(chǔ)在元類中
。去元類
里看看:
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100002230
(lldb) x/4gx 0x0000000100002230
0x100002230: 0x0000000100002208 0x00000001003f0190
0x100002240: 0x000000010142d880 0x0001802400000003
類 LGPerson
的isa指針
地址是0x0000000100002208
,偏移32位獲取class_data_bits_t
,即0x0000000100002228
,再打印出這個(gè)地址對(duì)應(yīng)的方法列表信息:
(lldb) p (class_data_bits_t *)0x0000000100002228
(class_data_bits_t *) $1 = 0x0000000100002228
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010142d7e0
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975536
}
firstSubclass = nil
nextSiblingClass = 0x00007fff8dcd5c60
}
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002078
arrayAndFlag = 4294975608
}
}
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x0000000100002078
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayByebye"
types = 0x0000000100000f35 "v16@0:8"
imp = 0x0000000100000d50 (KCObjc`+[LGPerson sayByebye])
}
}
}
count = 1
只有1個(gè)方法sayByebye
。給自己一個(gè)666,哈哈!印證了isa的走位!
補(bǔ)充:objc_class繼承objc_object
struct objc_class : objc_object {
// 省略部分代碼.......
}
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
分析:
- 結(jié)構(gòu)體類型
objc_class
繼承自objc_object
類型,其中objc_object
也是一個(gè)結(jié)構(gòu)體,且有一個(gè)isa屬性,所以objc_class也擁有了isa屬性;
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
NSObject
中的isa在底層是Class
類型,而Class
的底層編碼來自objc_class
類型,所以NSObject
也擁有了isa指針
;由
NSObject
類初始化一個(gè)實(shí)例對(duì)象objc,objc 滿足 objc_object 的特性(即有isa指針
),主要是因?yàn)?code>isa 是NSObject
從objc_class
繼承過來的,而objc_class
繼承自objc_object
,objc_object
有isa指針
。所以對(duì)象都有一個(gè)isa
,isa
表示指向,來自于當(dāng)前的objc_object
;objc_object
就好比一個(gè)模板,不論是Class
對(duì)象還是繼承NSObject
的對(duì)象,都最終指向繼承objc_object
(實(shí)質(zhì)是繼承了成員isa
),所以說萬(wàn)物皆對(duì)象。
總結(jié)
1.首先從底層源碼分析了isa
指針的走位,并且通過示例得以證明。
2.通過isa
的走位,我們分析了類Class
的底層結(jié)構(gòu)objc_class
,通過示例打印屬性列表信息和方法列表信息,證明了實(shí)例方法存儲(chǔ)在類中,類方法存儲(chǔ)在元類中
。
3.最后通過objc_class
與objc_object
的繼承關(guān)系,得出萬(wàn)物皆對(duì)象,但始終離不開isa
。