知識要點:
Clang簡介
對象本質(zhì)
類和對象如何關(guān)聯(lián)(initIsa)
Clang
Clang是一個C語言、C++捎琐、Objective-C語言的輕量級編譯器。源代碼發(fā)布于BSD協(xié)議下砸彬。 Clang將支持其普通lambda表達(dá)式造垛、返回類型的簡化處理以及更好的處理constexpr關(guān)鍵字。
Clang是一個由Apple主導(dǎo)編寫姻乓,基于LLVM C/C++/Objective-C編譯器
2013年4月,Clang已經(jīng)全面支持C++11標(biāo)準(zhǔn)嵌溢,并開始實現(xiàn)C++1y特性(也就是C++14,這是 C++的下一個小更新版本)蹋岩。Clang將支持其普通lambda表達(dá)式赖草、返回類型的簡化處理以及更 好的處理constexpr關(guān)鍵字。
Clang是一個C++編寫剪个、基于LLVM秧骑、發(fā)布于LLVM BSD許可證下的C/C++/Objective-C/ Objective-C++編譯器。它與GNU C語言規(guī)范幾乎完全兼容(當(dāng)然扣囊,也有部分不兼容的內(nèi)容乎折, 包括編譯命令選項也會有點差異),并在此基礎(chǔ)上增加了額外的語法特性侵歇,比如C函數(shù)重載 (通過attribute((overloadable))來修飾函數(shù))骂澄,其目標(biāo)(之一)就是超越GCC。
對象本質(zhì)
定義一個對象惕虑,使用clang -rewrite-objc main.m -o main.cpp 把目標(biāo)文件編譯成c++文件
編譯時可能會遇到以下問題:
UIKit報錯問題
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
安裝的時候順帶安裝了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ī))
編譯之后枷遂,你會看到上萬行代碼樱衷,不要慌,直接搜索類名酒唉,找到如下代碼(在Mac上調(diào)試):
#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
結(jié)論:對象的本質(zhì)是結(jié)構(gòu)體
類到底是如何被關(guān)聯(lián)的
在alloc與init探索中說到矩桂,類與開辟的空間是通過initInstanceIsa關(guān)聯(lián)起來的,但是,沒有詳細(xì)探索侄榴。上源碼:
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#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
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// 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;
}
}
1.為了探索需要雹锣,先看看isa的定義:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
//ISA_BITFIELD定義
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
思考
apple在定義isa時,為什么要考慮使用聯(lián)合體癞蚕,而不使用結(jié)構(gòu)體?
我們知道蕊爵,現(xiàn)在幾乎都支持64位,64位可以存儲2的64次方個數(shù)據(jù)桦山。但是一個類的基本信息用這64位完全夠用攒射,不需要定義一個結(jié)構(gòu)體,在結(jié)構(gòu)體中定義一個個成員來存儲類的信息恒水。這樣做会放,大大減少了內(nèi)存,畢竟一個工程所創(chuàng)建的類還是比較多的钉凌。
注意:
isa是存儲類的信息咧最,并不包括屬性、方法等御雕,這些信息在其Class中
2.接下來矢沿,我們進(jìn)行驗證
2.1)定義一個Person類
@interface Person : NSObject
@end
#import "Person.h"
@implementation Person
@end
2.2)在之前配置好的objc源碼中,找到mian函數(shù)酸纲,引入Person并初始化捣鲸,下斷點:
2.3)在initInstanceIsa、initIsa方法中斷點
注意:開始運(yùn)行前福青,這兩個斷點先去掉,等走到Person的斷點位置脓诡,再放開這兩個斷點无午,會進(jìn)入initIsa:
2.4)斷點繼續(xù)走,我們看到給isa賦初始值:
2.5)輸出isa:
3.見證奇跡
3.1)ISA_MAGIC_VALUE是:0x001d800000000001ULL祝谚,轉(zhuǎn)換為二進(jìn)制:
3.2)將ISA_MAGIC_VALUE轉(zhuǎn)換為二進(jìn)制宪迟,isa賦值之后,根據(jù)ISA_BITFIELD(isa位域)對應(yīng)的位置表示:
3.3)乍一看交惯,覺得cls和bits不同次泽,違反了聯(lián)合體的互斥原則,其實是相同的席爽,只不過一個是16機(jī)制表示意荤,一個是10進(jìn)制表示。將上圖的值與isa打印的值比較只锻,是一一對應(yīng)的
3.4)每一個位域表示的含義
nonpointer
:表示是否對 isa 指針開啟指針優(yōu)化 0:純isa指針玖像,1:不止是類對象地址,isa 中包含了類信息、對象的引用計數(shù)等has_assoc
:關(guān)聯(lián)對象標(biāo)志位齐饮,0沒有捐寥,1存在has_cxx_dtor:該對象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯, 如果沒有,則可以更快的釋放對象
shiftcls
:存儲類指針的值笤昨。開啟指針優(yōu)化的情況下,在 arm64 架構(gòu)中有 33 位用來存儲類指針握恳。extra_rc
:當(dāng)表示該對象的引用計數(shù)值瞒窒,實際上是引用計數(shù)值減 1, 例如乡洼,如果對象的引用計數(shù)為 10崇裁,那么 extra_rc 為 9。如果引用計數(shù)大于 10就珠, 則需要使用到下面的 has_sidetable_rc寇壳。magic
:用于調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間weakly_referenced
:志對象是否被指向或者曾經(jīng)指向一個 ARC 的弱變量,沒有弱引用的對象可以更快釋放妻怎。deallocating
:標(biāo)志對象是否正在釋放內(nèi)存has_sidetable_rc
:當(dāng)對象引用技術(shù)大于 10 時壳炎,則需要借用該變量存儲進(jìn)位3.5)對shiftcls賦值
(uintptr_t)cls
是將cls轉(zhuǎn)為10進(jìn)制,我們再次打印isa:為什么要右移3位?是要把位域中的最后三位抹除逼侦,驗證:
既然右移3位將第三位抹零匿辩,因為shitcls占44位,左移20位榛丢,將高20位抹零铲球。由于先右移3后左移20,所以晰赞,需要右移17位回歸到原位置稼病,然后輸出,你發(fā)現(xiàn)他就是Person:
可能你對左移右移迷糊掖鱼,看下圖
Apple早就想到移來移去太麻煩然走,定義了一個掩碼ISA_MASK,直接將isa的shitcls與上掩碼(高17位低3位都是0戏挡,中間44位是1)芍瑞,就可得到cls:
思考
isa明明是一個聯(lián)合體,為什么在對象中是一個Class類型褐墅?
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
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;
其實拆檬,本質(zhì)是一樣的。我們在使用的時候妥凳,都是Class竟贯,這是對我們直接訪問類的屬性或方法是方便的,所以逝钥,獲取類時會將isa的聯(lián)合體轉(zhuǎn)換為Class
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}