聯(lián)合體
聯(lián)合體(共用體):一種特殊的數(shù)據(jù)類型像捶,允許在相同的內(nèi)存位置存儲(chǔ)不同的數(shù)據(jù)類型上陕。
- 所有成員占用同一段內(nèi)存,修改一個(gè)成員會(huì)影響其余所有成員拓春。
共用體使用了內(nèi)存覆蓋技術(shù)
释簿,同一時(shí)刻只能保存一個(gè)成員
的值,如果對(duì)新的成員賦值硼莽,就會(huì)把原來成員的值覆蓋掉
庶溶。 - 各變量是
互斥
的——缺點(diǎn)就是不夠包容
; 但優(yōu)點(diǎn)是內(nèi)存使用更為精細(xì)靈活
,也節(jié)省
了內(nèi)存空間懂鸵。 - 共用體占用的內(nèi)存等于
最長(zhǎng)的成員占用的內(nèi)存
偏螺。
Clang
-
Clang
是一個(gè)由Apple主導(dǎo)編寫,基于LLVM
的C/C++/Objective-C
編譯器 -
作用:借助
Clang
可以將oc
文件輸出成C++
文件匆光,方便探究其底層的一些結(jié)構(gòu)套像、邏輯、底層的實(shí)現(xiàn)原理等终息。
簡(jiǎn)單的使用命令
//把目標(biāo)文件編譯成c++文件
clang -rewrite-objc main.m -o main.cpp
// UIKit報(bào)錯(cuò)問題
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
`xcode`安裝的時(shí)候順帶安裝了`xcrun`命令夺巩,`xcrun`命令在`clang`的基礎(chǔ)上進(jì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ī))
接下來借助Clang
來簡(jiǎn)單的探索一下OC的對(duì)象采幌。
oc對(duì)象探索
- 準(zhǔn)備
oc
對(duì)象LGPerson
,并添加屬性name
劲够。此時(shí)LGPeson
應(yīng)該有name
和isa
兩個(gè)成員。
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
@property (copy, nonatomic) NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
- 轉(zhuǎn)化輸出
C++
文件,找到LGPerson
對(duì)象
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
// @property (copy, nonatomic) NSString *name;
/* @end */
// @implementation LGPerson
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }
// @end
- 從輸出的
C++
文件可以看到LGPerson
是objc_object
的結(jié)構(gòu)體休傍。
可以看到定義的屬性_name
,但是沒有發(fā)現(xiàn)isa
蹲姐。反而有一個(gè)struct NSObject_IMPL NSObject_IVARS;
磨取。
在c++
文件中搜索NSObject_IMPL {
會(huì)發(fā)現(xiàn)缺少的isa
在里面。但是卻變成了Class
類型柴墩。
struct NSObject_IMPL {
Class isa;
};
我們?cè)谔剿?a href="http://www.reibang.com/p/ee56f6d3d4a9" target="_blank">alloc &init時(shí)忙厌,有initInstanceIsa
是將isa
與cls
相關(guān)聯(lián)。那么應(yīng)該是在關(guān)聯(lián)這里做什么江咳。下面進(jìn)入源碼探索一下關(guān)聯(lián)的操作逢净。
isa關(guān)聯(lián)cls
-
isa
和cls
的源碼關(guān)聯(lián)操作
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
可以看到isa
和cls
的關(guān)聯(lián),其實(shí)就是將cls
向右移動(dòng)了3位,賦值
保存在isa
的shiftcls
的區(qū)域中爹土。因此LGPerson
其實(shí)就保存在isa
中甥雕。
那么通過object_getClass
獲取Class
時(shí),源碼底層肯定是從isa
中通過某種操作取出我們需要的Class
胀茵。
進(jìn)入object_getClass
源碼
可以發(fā)現(xiàn)object_getClass
方法就是獲取isa
社露。
最終發(fā)現(xiàn)是isa
的bits
成員或者&ISA_MASK
的結(jié)果在轉(zhuǎn)成Class
返回。
isa是如何存儲(chǔ)cls
-
isa
類型是isa_t
是一個(gè)聯(lián)合體(union
)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits 琼娘,兩者是互斥關(guān)系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
從isa_t的定義有Class cls
和一個(gè)uintptr_t bits
互斥的成員及結(jié)構(gòu)體中宏定義的位域ISA_BITFIELD
-
nonpointer
:表示是否對(duì)isa指針
開啟指針優(yōu)化
0:純isa指針
1:不止是類對(duì)象地址,isa
中包含了類信息峭弟、對(duì)象的引用計(jì)數(shù)
等 -
has_assoc
:關(guān)聯(lián)對(duì)象標(biāo)志位,0沒有脱拼,1存在 -
has_cxx_dtor
:該對(duì)象是否有C++
或者Objc
的析構(gòu)器
,如果有析構(gòu)函數(shù)
,則需要做析構(gòu)邏輯
, 如果沒有
,則可以更快的釋放對(duì)象
瞒瘸。 -
shiftcls
:存儲(chǔ)類指針的值。開啟指針優(yōu)化
的情況下熄浓,在arm64
架構(gòu)中有33位
用來存儲(chǔ)類指針情臭。 -
magic
:用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象
還是沒有初始化的空間
-
weakly_referenced
:對(duì)象是否被指向或者曾經(jīng)指向
一個(gè)ARC
的弱變量,沒有弱引用的對(duì)象可以更快釋放
-
deallocating
:標(biāo)志對(duì)象是否正在釋放內(nèi)存 -
has_sidetable_rc
:當(dāng)對(duì)象引用技術(shù)大于10 時(shí)玉组,則需要借用該變量存儲(chǔ)進(jìn)位 -
extra_rc
:當(dāng)表示該對(duì)象的引用計(jì)數(shù)值
谎柄,實(shí)際上是引用計(jì)數(shù)值減 1
.
例如,如果對(duì)象的引用計(jì)數(shù)為 10惯雳,那么extra_rc
為 9朝巫。如果引用計(jì)數(shù)大于 10, 則需要使用到下面的has_sidetable_rc
arm64
下ISA_BITFIELD
位域的存儲(chǔ)的信息
位域中shiftcls
位置的33
存儲(chǔ)類的指針的值石景。那么取出isa
存儲(chǔ)的位域中的shiftcls
的值劈猿,應(yīng)該是我們的存儲(chǔ)的Class
。
x86_64
下存儲(chǔ)cls
的流程
- 斷點(diǎn)
cls
為LGPerson
時(shí)進(jìn)入isa
源碼
斷點(diǎn)newisa
的初始化isa_t newisa(0);
賦初值的操作newisa.bits = ISA_MAGIC_VALUE;
分別輸出:
從結(jié)果可以看到cls和bits
賦值后揪荣,位域中存儲(chǔ)的信息,也發(fā)生了變化往史。nonpointer
代表對(duì)isa
開啟指針優(yōu)化仗颈。magic
值成了59
。因?yàn)檫@里只是進(jìn)行了賦初值那么這59
肯定來自初始值ISA_MAGIC_VALUE
椎例。
從x86_64
結(jié)構(gòu)圖中看magic
是從47位
開始的占6位
挨决。將初始值ISA_MAGIC_VALUE
輸出二進(jìn)制進(jìn)行查看《┩幔看到從magic
位置的二進(jìn)制111011
是十進(jìn)制的59
脖祈。
-
cls
的賦值newisa.shiftcls = (uintptr_t)cls >> 3;
將cls
轉(zhuǎn)化成uintptr_t
右移3位存儲(chǔ)在shiftcls
區(qū)域。
了解了cls
的存儲(chǔ)的位置及操作刷晋。那么通過存儲(chǔ)的逆向操作應(yīng)該能從isa
中取出LGPerson
盖高。
- 從
isa
中獲取LGPerson
通過反向?qū)?code>isa的反向操作確實(shí)獲得了LGPerson
慎陵。
但是通過object_getClass
獲取Class
時(shí)是isa.bits& ISA_MASK
操作。
return (Class)(isa.bits & ISA_MASK);
ISA_MASK = 0x00007ffffffffff8ULL
其實(shí)就是上面的移動(dòng)抹0
操作的位與運(yùn)算
喻奥。遮住不需要的信息席纽,保留需要的信息。
通過isa
的與cls
的關(guān)聯(lián)存儲(chǔ)及object_getClass
獲取源碼也就可以理解c++
文件中isa
是Class
類型了映凳。
struct NSObject_IMPL {
Class isa;
};