一 對(duì)象 isa 類 元類
上代碼
//MARK: - 分析類在內(nèi)存存在個(gè)數(shù)
void lgTestClassNum(){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
/*
打印結(jié)果:
0x100003270-
0x100003270-
0x100003270-
0x100003270
*/
}
上述代碼中,通過(guò)三種不同方式創(chuàng)建LGPerson
三個(gè)類對(duì)象,但三個(gè)類對(duì)象的地址
一模一樣,說(shuō)明類在內(nèi)存中只有一個(gè)
.
那我們帶著上面的結(jié)論來(lái)看下面這組lldb打印結(jié)果
問(wèn)題 : 為什么Person
的地址有兩個(gè)
- 首先我們獲取了
person
對(duì)象的isa
地址 進(jìn)行p/x 0x001d8001000081d1 & 0x00007ffffffffff8ULL
操作 獲得person
對(duì)象的類地址0x00000001000081d0
跟我們進(jìn)行p/x person.class
的結(jié)果相同 -
0x00000001000081a8
為Person
類的isa
指向即為Person
類的元類
元類的說(shuō)明
下面來(lái)解釋什么是元類栋操,主要有以下幾點(diǎn)說(shuō)明:
我們都知道 對(duì)象的
isa
是指向類,類的其實(shí)也是一個(gè)對(duì)象柄慰,可以稱為類對(duì)象草娜,其isa
的位域指向蘋果定義的元類元類是系統(tǒng)給的善涨,其
定義和創(chuàng)建
都是由編譯器
完成诉儒,在這個(gè)過(guò)程中同欠,類的歸屬來(lái)自于元類
元類 是
類對(duì)象
的類虎囚,每個(gè)類都有一個(gè)獨(dú)一無(wú)二的元類用來(lái)存儲(chǔ) 類方法的相關(guān)信息淮椰。元類本身是沒(méi)有
名稱
的五慈,由于與類相關(guān)聯(lián),所以使用了同類名
一樣的名稱
如果我們繼續(xù)進(jìn)行isa
執(zhí)行溯源 最終可得出以下結(jié)論 這里就不截圖了有興趣的同學(xué)可以自己驗(yàn)證下
- isa指向 :
對(duì)象 --> 類 --> 元類 --> NSobject(根元類) -->NSObject 指向自身
- 繼承關(guān)系:
子類 --> 父類 --> 根類(NSobject) --> nil
- 元類繼承關(guān)系 :
子元類 --> 父元類 --> 根元類(NSobject) --> NSobject --> nil
著名的 isa走位 & 繼承關(guān)系 圖
代碼驗(yàn)證 :
// NSObject實(shí)例對(duì)象
NSObject *object1 = [NSObject alloc];
// NSObject類
Class class = object_getClass(object1);
// NSObject元類
Class metaClass = object_getClass(class);
// NSObject根元類
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元類
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 實(shí)例對(duì)象\n%p 類\n%p 元類\n%p 根元類\n%p 根根元類",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
/*
打印結(jié)果:
0x1007160b0 實(shí)例對(duì)象
0x7fff98847118 類
0x7fff988470f0 元類
0x7fff988470f0 根元類
0x7fff988470f0 根根元類
*/
二 類的結(jié)構(gòu)
我們新建一個(gè) macOS 控制臺(tái)項(xiàng)目主穗,然后新建兩個(gè)類 Teacher 和 Student 出來(lái)泻拦,其中 Teacher 繼承自 NSObject, Student 繼承自 Teacher。
#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [Student alloc];
NSLog(@"stu: %@", stu);
}
return 0;
}
使用clang命令
clang -rewrite-objc main.m -o main.cpp
擴(kuò)展
如果是目標(biāo)文件導(dǎo)入了
UIKit
框架忽媒,則我們需要使用clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk main.m
, 其中 13.7 要根據(jù)當(dāng)前的模擬器SDK版本來(lái)做更改聪轿。其中
xcode
安裝的時(shí)候順帶安裝了 xcrun 命令,xcrun
命令在 clang 的基礎(chǔ)上進(jìn)行了一些封裝猾浦,要更簡(jiǎn)單好用一些陆错。
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
(模擬器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
(手機(jī))
這個(gè)命令是將我們的 main.m
文件編譯成C++
文件main.cpp
,我們打開(kāi)這個(gè)文件搜索 Student
,我們發(fā)現(xiàn)有多個(gè)地方都出現(xiàn)了 Student金赦,然后我們使用全局搜索關(guān)鍵字typedef struct objc_object
音瓷,最終我們找到了class
的定義
typedef struct objc_class *Class;
然后去 源碼中直接搜索 struct objc_class
刨裆,然后定位到 objc-runtime-new.h
文件
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();
}
// 省略部分代碼.......
}
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
可以看到objc_class
是繼承于objc_obje
ct的传轰,而objc_class本身是沒(méi)有isa
的,其isa繼承自objc_object
,對(duì)象又是由類創(chuàng)建
的
由此可知objc_object
與 對(duì)象的關(guān)系彩匕,對(duì)象漠烧、類杏愤、元類都有is
a,那么可以說(shuō)所有的對(duì)象都是按照objc_object
的模板繼承
而來(lái)的,所有的對(duì)象都繼承于objc_object.
三 類結(jié)構(gòu)探索
struct objc_class : objc_object {
// Class ISA; //8字節(jié)
Class superclass; //Class 類型 8字節(jié)
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略已脓,未貼出
}
isa
屬性:繼承自objc_object
的isa
珊楼,占8
字節(jié)superclass
屬性:Class
類型,Class是由objc_object
定義的度液,是一個(gè)指針厕宗,占8字節(jié)cache
屬性 : 結(jié)構(gòu)體類型 大小要看內(nèi)部結(jié)構(gòu)bits
屬性:只有首地址經(jīng)過(guò)上面3個(gè)屬性的內(nèi)存大小總和的平移画舌,才能獲取到bits
cache大小分析
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一個(gè)結(jié)構(gòu)體指針類型,占8字節(jié)
explicit_atomic<mask_t> _mask; //是mask_t 類型已慢,而 mask_t 是 unsigned int 的別名曲聂,占4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指針,占8字節(jié)
mask_t _mask_unused; //是mask_t 類型佑惠,而 mask_t 是 uint32_t 類型定義的別名朋腋,占4字節(jié)
#if __LP64__
uint16_t _flags; //是uint16_t類型,uint16_t是 unsigned short 的別名膜楷,占 2個(gè)字節(jié)
#endif
uint16_t _occupied; //是uint16_t類型旭咽,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)
總結(jié):所以最后計(jì)算出cache類的內(nèi)存大小 = 12 + 2 + 2 = 16
字節(jié)
bits
通過(guò)類的首地址平移32字節(jié)獲取 class_data_bits_t*:
(lldb) p/x Person.class
(Class) $0 = 0x00000001000082d0 Person
//首地址平移32字節(jié)
(lldb) p (class_data_bits_t*) 0x1000082f0
(class_data_bits_t *) $1 = 0x00000001000082f0
(lldb) p $1-> data()
//data方法獲取bits
(class_rw_t *) $2 = 0x000000010072e1d0
現(xiàn)在我們獲取到 class_data_bits_t
結(jié)構(gòu)體中的class_rw_t
繼續(xù)探索源碼發(fā)現(xiàn)結(jié)構(gòu)體中有提供相應(yīng)的方法去獲取 屬性列表把将、方法列表等轻专,如下所示
通過(guò)class_rw_t
提供的方法忆矛,繼續(xù)探索bits
中的屬性列表
(lldb) p $2.properties()
(const property_array_t) $3 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008238
}
arrayAndFlag = 4295000632
}
}
}
Fix-it applied, fixed expression was:
$2->properties()
(lldb) p $3.list
(const RawPtr<property_list_t>) $4 = {
ptr = 0x0000000100008238
}
(lldb) p $4.ptr
(property_list_t *const) $5 = 0x0000000100008238
(lldb) p *$5
(property_list_t) $6 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 3)
}
(lldb) p $6.get(0)
(property_t) $7 = (name = "fafd", attributes = "T@\"NSString\",C,N,V_fafd")
(lldb) p $6.get(1)
(property_t) $8 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $6.get(2)
(property_t) $9 = (name = "sex", attributes = "T@\"NSString\",C,N,V_sex")
(lldb) p $6.get(3)
Assertion failed: (i < count), function get, file /Users/caomengfei/Downloads/objc4-debugTest-master/objc4-818.2/runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
-+
OK 現(xiàn)在我們的三個(gè)屬性已經(jīng)都找到了 繼續(xù)嘗試下 方法列表 分為類方法
和實(shí)例方法
,與屬性一樣,這次試用
methods
獲取
現(xiàn)在我們打印出共 8
個(gè)方法 3
個(gè)屬性的set get方法
我們添加的一個(gè)setPersonName
實(shí)例方法
問(wèn)題 成員方法 與類方法存儲(chǔ)在何處
觀察源碼發(fā)現(xiàn)class_rw_t
中 class_ro_t
這個(gè)屬性察蹲,通過(guò)查看其定義,發(fā)現(xiàn)其中有一個(gè)ivars
屬性催训,我們可以做如下猜測(cè):是否成員變量
就存儲(chǔ)在這個(gè)ivar_list_t
類型的ivars
屬性中呢洽议?
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_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
struct class_ro_t {
uint32_t flags; //4
uint32_t instanceStart;//4
uint32_t instanceSize;//4
#ifdef __LP64__
uint32_t reserved; //4
#endif
const uint8_t * ivarLayout; //8
const char * name; //1 ? 8
method_list_t * baseMethodList; // 8
protocol_list_t * baseProtocols; // 8
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
//方法省略
}
上圖可知ivars
屬性,其中的count
為4 ,除了age
還有_name ,_sex等成員變量
通過(guò)
{}
定義的成員變量
漫拭,會(huì)存儲(chǔ)在類的bits
屬性中亚兄,通過(guò)bits --> data() -->ro() --> ivars
獲取成員變量列表,除了包括成員變量采驻,還包括屬性定義
的成員變量通過(guò)
@property
定義的屬性审胚,也會(huì)存儲(chǔ)在bits
屬性中,通過(guò)bits --> data() --> properties() --> lis
獲取屬性列表礼旅,其中只包含屬性
類方法列表(類方法存儲(chǔ)在元類中,從元類中獲取)有興趣的小伙伴可以驗(yàn)證下