做過iOS開發(fā)的同學(xué)都應(yīng)該知道我們平時(shí)編寫的OC代碼的底層實(shí)現(xiàn)都是通過C/C++實(shí)現(xiàn)的,所以O(shè)C的對象都是基于C/C++的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的
1、那么OC的對象鹦马、類都是基于C/C++的什么數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的呢?
我們來建立一個(gè)項(xiàng)目將OC的代碼轉(zhuǎn)化到C/C++的代碼來看一下
? ? 1)建立一個(gè)簡單的項(xiàng)目:
????2)打開終端進(jìn)入項(xiàng)目文件夾下執(zhí)行命令:$ clang -rewrite-objc main.m -o main.cpp
主意一定要生成cpp文件,如果生成c文件的話會(huì)丟失很多東西的
但是這樣生成的話main.cpp文件會(huì)比較大藐守,所以建議用xcode的一個(gè)指令進(jìn)行指定在什么架構(gòu)下的生成文件:$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
????3)打開main-arm64.cpp文件,搜索NSObject可以查看到
所以可以看到OC的對象其實(shí)轉(zhuǎn)為C/C++的結(jié)構(gòu)體實(shí)現(xiàn)的蹂风,而結(jié)構(gòu)體中只包含一個(gè)isa的指針變量卢厂,那么根據(jù)C語言的知識(shí)可以看到,isa的指針就是NSObject初始化對象的地址(其實(shí)NSObject的方法和其他的信息都存在其他的地方并不在對象的空間內(nèi)惠啄,后邊會(huì)說)慎恒,那么從目前來看一個(gè)NSObject的對象所占用的空間就是一個(gè)指針?biāo)嫉目臻g(64位下為8個(gè)字節(jié),32位為4個(gè)字節(jié))
D於伞H诩怼!主意:其實(shí)8個(gè)字節(jié)是不嚴(yán)謹(jǐn)?shù)那骶啵筮吔忉專粒氧。。?/p>
2节腐、接下來我們創(chuàng)建兩個(gè)NSObject的子類外盯,Student和Person類摘盆,student繼承person
? ? 2.1那么這兩個(gè)類的存儲(chǔ)空間是多少呢?
? ? 我們知道一個(gè)對象其實(shí)就是一個(gè)C/C++的結(jié)構(gòu)體饱苟,那么我們將這兩個(gè)類和上邊一樣的命令進(jìn)行轉(zhuǎn)化一下發(fā)現(xiàn):
通過分析person實(shí)例化的對象的內(nèi)存為:4 +8 =12
student實(shí)例化的對象的內(nèi)存為:4 +12 =16
其實(shí)是不對的孩擂,這里會(huì)涉及內(nèi)存對齊的概念,那么我們來看一下這兩個(gè)對象到底占用了多少個(gè)字節(jié)呢箱熬?我們通過代碼來看一下:
所以真實(shí)的占用內(nèi)存的大小都是16肋殴,這是因?yàn)閮?nèi)存對齊的原因,對于內(nèi)存對齊大家可以百度查看坦弟。你自己看一下NSObject的實(shí)例對象是多大护锤,其實(shí)是8,所以也證明了對象的內(nèi)存中只存在成員變量的值酿傍,不保存其他的信息烙懦。
????2.2我們還可以對對象的占用內(nèi)存細(xì)節(jié)借助xcode看一下:
? ? 1)打上斷點(diǎn)然后View->Memory如圖:
? ? 2)
? ? ? ? 由此也能看出stu的實(shí)力化對象一共所占用16個(gè)字節(jié),_age =06 00 00 00? ? ?_no =09 00 00 00
? ? ? ? 所以再次證明:一個(gè)類的實(shí)例化對象的內(nèi)存只保存屬性值赤炒,不會(huì)包含其他信息
3氯析、由上邊的一系列操作可以看出一個(gè)類的實(shí)例化對象的內(nèi)存只保存屬性值,不會(huì)包含其他信息莺褒,但是其他的類的信息都保存在哪兒呢掩缓?而且上邊還有一個(gè)細(xì)節(jié)那就是實(shí)例化對象的isa的值(指針)到底指向哪兒?
先說結(jié)論:Objective-C中的對象遵岩,簡稱OC對象你辣,主要可以分為3種
? ? ? ? ? ? ?1)instance對象(實(shí)例對象)就是通過類alloc出來的對象,每次調(diào)用alloc都會(huì)產(chǎn)生新的instance對象尘执,instance對象在內(nèi)存中存儲(chǔ)的信息包括舍哄、isa指針、其他成員變量
? ? ? ? ? ? ?2)class對象(類對象)每個(gè)類在內(nèi)存中有且只有一個(gè)class對象誊锭,class對象在內(nèi)存中存儲(chǔ)的信息主要包含:isa指針表悬、superclass指針、類的屬性信息(@property)丧靡、類的對象方法信息(instance method)蟆沫、類的協(xié)議信息(protocol)、類的成員變量信息(ivar)温治、......
? ? ? ? ? ? ?3)meta-class對象(元類對象)meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的饭庞,但是用途不一樣。meta-class對象在內(nèi)存中存儲(chǔ)的信息主要包含:isa指針罐盔、superclass指針但绕、類的類方法信息(class method)、......
? ? 3.1我們先證明兩個(gè)個(gè)細(xì)節(jié):1)每個(gè)類的內(nèi)存中有且只有一個(gè)class對象
? ? ? ? ? ? ? ? 2)meta_class對象在內(nèi)存中也是一個(gè)但和class對象不是一個(gè)內(nèi)存哦,他們都是class 類型捏顺,所以他們的結(jié)構(gòu)都是一樣的六孵。內(nèi)存是一個(gè)可以分析出來,因?yàn)閏lass的對象都是一個(gè)幅骄,而獲取元類的對象都是從class對象獲取的劫窒,所以肯定是一樣的。
獲取meta_class的方法:
????????Class metaClass =object_getClass([NSObject class]);//獲取類對象的類就是元類拆座,但是不能[[NSObject class]class]這樣獲取主巍,這樣獲取永遠(yuǎn)只是類對象!只能通過runtime的方法獲扰泊铡孕索!
查看Class是否為meta-class:
? ? 3.2再來看一下各個(gè)對象的isa指針到底指向誰呢?
借用網(wǎng)上一張圖:
由圖看出來instance對象的isa指向class躏碳,class對象的isa指向meta_class搞旭。由此可見:oc的對象的方法調(diào)用就可以串起來了,例如之前的student的instance對象調(diào)用對象方法:
Student *stu =[[Student? alloc]init];
[stu? studentMethod];
stu的指針就是isa的指針菇绵,而isa指向student的class肄渗,那么其實(shí)就是student的class對象調(diào)用的studentMethod,所以就可以分析出一個(gè)類的對象方法信息其實(shí)放在class中的咬最,分析一下:因?yàn)樗械膶?shí)例化對象的方法都一樣翎嫡,沒必要在每個(gè)對象中都開辟一份內(nèi)存空間來放方法信息,因?yàn)檫@是對內(nèi)存利用不科學(xué)的永乌,所以放在class對象中是比較好的
接下來我們證明一下
? ? 1)新建項(xiàng)目:(這里面的lldb指令我就不再解釋了)
很明顯stu的isa的指針和stu的class對象的地址是不一樣的惑申,跟我們上邊說的結(jié)論不一樣,那是因?yàn)閺膇sa指針到class的地址是要做一次位運(yùn)算的铆遭,根據(jù)架構(gòu)不同&的值一不一樣:
如果想直接看到ISA_MASK的值的話硝桩,可以到apple官網(wǎng)下載runtime的源碼查找,這里就不在尋找了枚荣,直接驗(yàn)證:
通過一次位運(yùn)算之后很明顯的結(jié)果是一樣的,說明之前的結(jié)論是對的啼肩,我這兒是模擬器所以使用的是第二個(gè)ISA_MASK值橄妆,你如果使用真機(jī)測試的話那么請使用第一個(gè)ISA_MASK值進(jìn)行運(yùn)算。
接下來的Person和NSObject的驗(yàn)證可以自己進(jìn)行祈坠。還有那幅圖的superclass的指向都可以通過LLDB指令進(jìn)行驗(yàn)證:接下來我們驗(yàn)證一下superclass的那條線的指向:
很明顯指向跟上邊的那個(gè)圖是一樣的
然后我們在去看一下calss對象里面都有什么害碾,其實(shí)可以從之前從apple官網(wǎng)下載的代碼可以找到的,這個(gè)不方便演示但是我們可以借助xcode來證明一下:我們來借助mj老師的文件MJClassInfo.h里面的mj_objc_class的結(jié)構(gòu)體來將class的對象強(qiáng)轉(zhuǎn)到結(jié)構(gòu)體來窺探下class里面的東西:(這里面會(huì)涉及一點(diǎn)c++的知識(shí)赦拘,你只要知道上邊的結(jié)論也就行了慌随,證明作為了解):
其他的信息自己可以看下就可以看出class和metaClass對象里面的信息了如圖:
(主意,在打印的屬性列表里面只顯示第一個(gè)屬性,其他的屬性需要操作指針位移運(yùn)算來訪問其他的屬性阁猜,作為了解丸逸。還有元類對象雖然有屬性列表,但是里面都是空的剃袍,因?yàn)樗旁陬悓ο罄锩娴幕聘铡_€有在上邊打印的里面method_list是個(gè)數(shù)組,調(diào)用的時(shí)候也是一個(gè)個(gè)往下尋找民效,但是如果經(jīng)常被調(diào)用的時(shí)候那么這個(gè)方法會(huì)放入一個(gè)cache里面用于快速調(diào)用)
至此我們已經(jīng)看到oc對象的本質(zhì)了憔维,但是我們前邊有個(gè)不嚴(yán)謹(jǐn)?shù)恼f法是:一個(gè)NSObject對象占用8個(gè)字節(jié),32bit下占用4個(gè)字節(jié)畏邢。其實(shí)底層做了改變业扒,其實(shí)是開辟了16個(gè)字節(jié),但是實(shí)際使用的的確是8或者4個(gè)字節(jié)舒萎。怎么看一下呢程储?在源碼里面可以看到這個(gè)函數(shù):
所以其實(shí)是一個(gè)對象至少分配16個(gè)字節(jié)!但是實(shí)際使用為8個(gè)字節(jié)逆甜,也可以用runtime的一函數(shù)
最后的最后留個(gè)問題:給NSObject增加一個(gè)對象方法虱肄,那么[Student test]和[NSObject test],通過類調(diào)用test方法會(huì)報(bào)錯(cuò)么?交煞?咏窿?(主意分析上邊網(wǎng)上的那張圖和一個(gè)方法的調(diào)用順序分析一下就知道了)
ps:這個(gè)文章其實(shí)是我的學(xué)習(xí)筆記了,在小碼哥學(xué)習(xí)李明杰老師的網(wǎng)絡(luò)授課做得筆記素征,所以用了好多mj老師的東西集嵌,再次聲明一下,感謝mj老師在it界耕耘御毅!