如何探究對(duì)象的本質(zhì)扇丛?
因?yàn)閛c的底層是c和c++實(shí)現(xiàn)的,clang可以將oc還原為c或者c++的代碼,所以通過(guò)clang可以看到一個(gè)對(duì)象的c或者c++的基層實(shí)現(xiàn)程拭。
Clang的擴(kuò)展,什么是Clang?
Clang: a C language family frontend for LLVM筷狼。Clang 項(xiàng)目為LLVM 項(xiàng)目的 C 語(yǔ)言家族(C瓶籽、C++、Objective C/C++埂材、OpenCL塑顺、CUDA 和 RenderScript)中的語(yǔ)言提供了語(yǔ)言前端和工具基礎(chǔ)結(jié)構(gòu)。提供了 GCC 兼容的編譯器驅(qū)動(dòng)程序和 MSVC 兼容的編譯器驅(qū)動(dòng)程序 俏险。
Clang編譯oc文件
模擬器版cpp文件生成命令:
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
生成了main-arm64.cpp文件:
查看文件的內(nèi)容严拒,我在main.m中聲明和實(shí)現(xiàn)了NNPerson這個(gè)類,對(duì)應(yīng)的main.cpp文件的代碼如下:
可以看到NNPerson生成了一個(gè)結(jié)構(gòu)體:
struct NNPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _myAge;
};
其中竖独,有一個(gè)NSObject_IMPL裤唠,還一個(gè)我在oc中定義的myAge。進(jìn)一步查看NSObject_IMPL是什么预鬓,全局搜索可以看到如下代碼塊:
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct classref *classref_t;
由此可見(jiàn)巧骚,一個(gè)對(duì)象的本質(zhì)是一個(gè)結(jié)構(gòu)體,里面主要有一個(gè)結(jié)構(gòu)體NSObject_IMPL格二,也就是isa劈彪,是一個(gè)objc_class指針。
在這里我們也看到了關(guān)于id的定義顶猜,就是一個(gè)objc_object指針沧奴,所以我們通常用id來(lái)聲明對(duì)象的時(shí),直接id聲明就好长窄,不用再加對(duì)象的星號(hào)了滔吠。
然后進(jìn)一步查找objc_class可以看到objc_class的定義如下:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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
通過(guò)這里可以看到objc_class繼承自objc_object,我們點(diǎn)進(jìn)去objc_object看到了objc_class的結(jié)構(gòu):
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
/// An opaque type that represents a category.
typedef struct objc_category *Category;
/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
#endif
看到這個(gè)挠日,但是在現(xiàn)在的oc版本中這段代碼已經(jīng)廢棄了疮绷,最前面有宏定義#if !OBJC_TYPES_DEFINED,然后查看這個(gè)OBJC_TYPES_DEFINED發(fā)現(xiàn)定義如下:
#ifdef _OBJC_OBJC_H_
#error include objc-private.h before other headers
#endif
#define OBJC_TYPES_DEFINED 1
#undef OBJC_OLD_DISPATCH_PROTOTYPES
#define OBJC_OLD_DISPATCH_PROTOTYPES 0
始終為1嚣潜,取非的話就是0就不會(huì)走這個(gè)地方了冬骚。
再搜索看到可以看大objc_class的結(jié)構(gòu)如下:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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
//此處省略下面的一堆方法等等等
}
objc_class繼承自objc_object,所以我們來(lái)看objc2在使用的objc_object的結(jié)構(gòu)懂算,查找如下:
struct objc_object {
private:
isa_t isa;
//此處省略下面的一堆定義 主要可以看到一個(gè)isa
}
isa為isa_t類型只冻,isa_t定義如下:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
isa_t是一個(gè)聯(lián)合體,所以下面說(shuō)下結(jié)構(gòu)體和共用體的區(qū)別计技。
結(jié)構(gòu)體和共用體
-結(jié)構(gòu)體:結(jié)構(gòu)體(Struct)是一種集合喜德,它里面包含了多個(gè)變量或數(shù)組,它們的類型可以相同垮媒,也可以不同舍悯,每個(gè)這樣的變量或數(shù)組都稱為結(jié)構(gòu)體的成員(Member)航棱。定義格式如下:
struct 結(jié)構(gòu)體名{
結(jié)構(gòu)體所包含的變量或數(shù)組
};
-共用體:共用體(Union)有時(shí)也被稱為聯(lián)合或者聯(lián)合體。定義格式如下:
union 共用體名{
成員列表
};
結(jié)構(gòu)體和共用體的區(qū)別在于:結(jié)構(gòu)體占用的內(nèi)存大于等于所有成員占用的內(nèi)存的總和(成員之間可能會(huì)存在縫隙)贱呐,共用體占用的內(nèi)存等于最長(zhǎng)的成員占用的內(nèi)存丧诺。共用體使用了內(nèi)存覆蓋技術(shù),同一時(shí)刻只能保存一個(gè)成員的值奄薇,如果對(duì)新的成員賦值驳阎,就會(huì)把原來(lái)成員的值覆蓋掉。
舉一個(gè)共用體的例子??:
union NNTestUnion{
int a;
char b;
double c;
}testUnion;
NSLog(@"占用的大小是:%lu",sizeof(testUnion));
testUnion.a = 5;
NSLog(@"a=%d,b=%c,c=%f",testUnion.a,testUnion.b,testUnion.c);
testUnion.b = 'a';
NSLog(@"a=%d,b=%c,c=%f",testUnion.a,testUnion.b,testUnion.c);
testUnion.c = 8.0;
NSLog(@"a=%d,b=%c,c=%f",testUnion.a,testUnion.b,testUnion.c);
打印如下:
可以根據(jù)上面的結(jié)果看到馁蒂,同一時(shí)刻只能保存一個(gè)成員的值呵晚。成員之間是互斥的
如果union的成員定義改成如下:
union NNTestUnion{
int a;
char b;
float c;
}testUnion;
那么占用的大小第一個(gè)打印變成:
2021-06-18 17:23:57.350435+0800 KCObjcBuild[48834:10108220] 占用的大小是:4
所以共用體占用的內(nèi)存等于最長(zhǎng)的成員占用的內(nèi)存。
位域
位域:有些數(shù)據(jù)在存儲(chǔ)時(shí)并不需要占用一個(gè)完整的字節(jié)沫屡,只需要占用一個(gè)或幾個(gè)二進(jìn)制位即可饵隙。例如開(kāi)關(guān)只有通電和斷電兩種狀態(tài),用 0 和 1 表示足以沮脖,也就是用一個(gè)二進(jìn)位金矛。正是基于這種考慮,C語(yǔ)言又提供了一種叫做位域的數(shù)據(jù)結(jié)構(gòu)勺届。
在結(jié)構(gòu)體定義時(shí)驶俊,我們可以指定某個(gè)成員變量所占用的二進(jìn)制位數(shù)(Bit),這就是位域免姿。
舉一個(gè)位域的例子??:
struct direction{
bool up:1;
bool down:1;
bool left:1;
bool right:1;
}direction1;
struct Direction{
bool up;
bool down;
bool left;
bool right;
}Direction1;
NSLog(@"direction1占用的大小是:%lu",sizeof(direction1));
NSLog(@"Direction1占用的大小是:%lu",sizeof(Direction1));
打印結(jié)果如下:
就很清楚的可以看到結(jié)果饼酿,指定完畢之后就節(jié)省了很多內(nèi)存
ISA_BITFIELD
在union isa_t 中有一個(gè)ISA_BITFIELD這個(gè),點(diǎn)進(jìn)去看一下這個(gè)的定義:
typedef unsigned long uintptr_t;
# if __arm64__
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# elif __x86_64__
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
可以看到ISA_BITFIELD是一個(gè)宏定義胚膊,所以那個(gè)isa_t中:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
可以替換為:
typedef unsigned long uintptr_t;
#if defined(ISA_BITFIELD)
struct {
// defined in isa.h
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
};
可以了解到故俐,isa的定義,它里面定義了一個(gè)位域。并且可以看到紊婉,x86_64和arm64下的位域定義是不一樣的药版,不過(guò)都是占滿了所有的64位(1+1+1+33+6+1+1+1+19 = 64,x86_64同理)喻犁,下面來(lái)說(shuō)明一下每一個(gè)位域參數(shù)的含義:
-nonpointer:表示是否對(duì)isa開(kāi)啟指針優(yōu)化 槽片。0代表是純isa指針,1代表除了地址外株汉,還包含了類的一些信息筐乳、對(duì)象的引用計(jì)數(shù)等歌殃。
-has_assoc:關(guān)聯(lián)對(duì)象標(biāo)志位乔妈。
-has_cxx_dtor:該對(duì)象是否有C++或Objc的析構(gòu)器,如果有析構(gòu)函數(shù)氓皱,則需要做一些析構(gòu)的邏輯處理路召,如果沒(méi)有勃刨,則可以更快的釋放對(duì)象。
-shiftcls:存在類指針的值股淡,開(kāi)啟指針優(yōu)化的情況下身隐,arm64位中有33位來(lái)存儲(chǔ)類的指針。
-magic:判斷當(dāng)前對(duì)象是真的對(duì)象還是一段沒(méi)有初始化的空間唯灵。
-weakly_referenced:是否被指向或者曾經(jīng)指向一個(gè)ARC的弱變量贾铝,沒(méi)有弱引用的對(duì)象釋放的更快。
-unused:標(biāo)志是否未被使用過(guò)埠帕。
-has_sidetable_rc:當(dāng)對(duì)象引用計(jì)數(shù)大于10時(shí)垢揩,則需要進(jìn)位。
-extra_rc:表示該對(duì)象的引用計(jì)數(shù)值敛瓷,實(shí)際上是引用計(jì)數(shù)減一叁巨。例如:如果引用計(jì)數(shù)為10,那么extra_rc為9呐籽。如果引用計(jì)數(shù)大于10锋勺,則需要使用has_sidetable_rc。
( 說(shuō)明:以上參數(shù)含義摘抄自網(wǎng)絡(luò)資料狡蝶,不是官方文檔解釋庶橱,我自己想查官方的解釋沒(méi)找到哇ε=(′ο`))) )*
類和isa是如何綁定的?
之前學(xué)習(xí)alloc的流程時(shí)牢酵,最后在方法_class_createInstanceFromZone中悬包,做了三件事情,一個(gè)是計(jì)算所需要的空間大小馍乙,一個(gè)是開(kāi)辟內(nèi)存空間布近,另外一個(gè)是isa的的初始化,和相關(guān)類進(jìn)行綁定丝格。方法是initIsa撑瞧,那么具體來(lái)看看這個(gè)方法里做了些什么。
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
根據(jù)我們前面介紹的ISA_BITFIELD各個(gè)位域的意義显蝌,可以了解到shiftcls是用來(lái)存放類指針的值预伺,magic是用來(lái)判斷這個(gè)類有沒(méi)有被初始化完畢。
那么上面的代碼可以看到曼尊,會(huì)先初始化一個(gè)isa_t酬诀,根據(jù)nonpointer,也就是是不是純的ISA指針進(jìn)行不同分支的初始化后續(xù)操作骆撇。
我這個(gè)走的是純指針的流程:
然后會(huì)來(lái)到setClass方法:
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
// Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
// No signing, just use the raw pointer.
uintptr_t signedCls = (uintptr_t)newCls;
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
// We're only signing Swift classes. Non-Swift classes just use
// the raw pointer
uintptr_t signedCls = (uintptr_t)newCls;
if (newCls->isSwiftStable())
signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
// We're signing everything
uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# else
# error Unknown isa signing mode.
# endif
shiftcls_and_sig = signedCls >> 3;
#elif SUPPORT_INDEXED_ISA
// Indexed isa only uses this method to set a raw pointer class.
// Setting an indexed class is handled separately.
cls = newCls;
#else // Nonpointer isa, no ptrauth
shiftcls = (uintptr_t)newCls >> 3;
#endif
}
我這個(gè)進(jìn)到setClass方法以后直接走的這句
shiftcls = (uintptr_t)newCls >> 3;
shiftcls這個(gè)的值是newCls右移三位得到瞒御,我查看相關(guān)資料說(shuō)這里是為了內(nèi)存對(duì)齊,指針占8字節(jié)所以是八字節(jié)對(duì)齊神郊。
此時(shí)我打印右移之前的和右移之后的內(nèi)存地址如下:
過(guò)掉斷點(diǎn)走到這個(gè)地方:
此時(shí)打印下newisa為:
根據(jù)顯示的地址肴裙,此時(shí)isa中的cls已經(jīng)關(guān)聯(lián)上了類趾唱,shiftcls也保存上了相關(guān)的類信息。
(雖然我還是有點(diǎn)迷糊蜻懦,再有新的理解會(huì)更新的甜癞,太難了哇??)