iOS底層原理之OC對象本質

一艾帐、涉及知識點

1.共用體(聯(lián)合體)

定義

在進行某些算法的C語言編程的時候誓沸,需要使幾種不同類型的變量存放到同一段內存單元中否副,也就是使用覆蓋技術,幾個變量互相覆蓋愕乎,以達到節(jié)省空間的目的阵苇,這種幾個不同的變量共同占用一段內存的結構叫共用體,又稱聯(lián)合體妆毕。

特點:

共用體和結構體有下列區(qū)別:
a.結構體的成員之間是共存的:各個成員占用不同的內存慎玖,它們互相之間沒有影響。

b.聯(lián)合體的成員之間是互斥的:所有成員共用同一段內存笛粘,修改一個成員的值趁怔,會影響其余所有成員。

c.結構體占用的內存:大于等于所有成員占用內存的總和(需要內存對齊)

d.聯(lián)合體占用的內存:等于最大的成員占用的內存薪前,同一時刻只能保存一個成員的值

聲明
#pragma mark 共用體
union WJUnion {
    int     a;
    float   b;
    char    c;
};

2.位域

定義

C語言允許在一個結構體中以位為單位來指定其成員所占內存長度润努,這種以位為單位的成員稱為“位段”或稱位域( bit field) 。利用位段能夠用較少的位數存儲數據示括。

特點:

a.位段成員的類型必須指定為unsigned或int類型铺浇;
b.若某一位段要從另一個字開始存放,用:0長度為0的空位段垛膝,作用就是使下一個位段從下一個存儲單位(視不同編譯系統(tǒng)而異)開始存放鳍侣;
c.一個位段必須存儲在同一存儲單元中丁稀,不能跨兩個單元;
d.可以定義無名字段例如":2"倚聚;
e.位段的長度不能大于存儲單元的長度线衫,也不能定義位段數組;
f.位段可以用整形格式符輸出惑折;
g.位段可以在數值表達式中引用授账,它會被系統(tǒng)自動地轉換成整形數;

聲明
#pragma mark 結構體
struct WJCarStruct {
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
};

#pragma mark 位域
struct WJCarStructBit {
    BOOL front  : 1;
    BOOL back   : 1;
    BOOL left   : 1;
    BOOL right  : 1;
};

分析:
結構體WJCarStruct占4個字節(jié),因為每個BOOL各占1個字節(jié)惨驶;
位域WJCarStructBit占1個字節(jié)白热,對比WJCarStruct結構體節(jié)省3個字節(jié)。

二粗卜、OC對象底層分析輔助工具(clang編譯源碼成底層代碼)

1.clang定義

clang簡而言之是一個C語言屋确、C++、Objective-C語言的輕量級編譯器休建,源代碼發(fā)布于BSD協(xié)議下乍恐。Clang將支持其普通lambda表達式、返回類型的簡化處理以及更好的處理constexpr關鍵字

2.在終端使用clang相關命令編譯WJPerson.m生成WJPerson.cpp底層文件

2.1 使用clang命令

# clang -rewrite-objc WJPerson.m -o WJPerson.cpp

2.2 使用Xcode中的xcrun命令

# xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc WJPerson.m -o WJPerson.cpp
3.打開WJPerson.cpp并找到WJPerson類
typedef struct objc_object WJPerson;
typedef struct {} _objc_exc_WJPerson;

extern "C" unsigned long OBJC_IVAR_$_WJPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_WJPerson$_nickName;
struct WJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;  //就是isa指針
    NSString * _Nonnull _name;        //屬性name對應的成員變量
    NSString * _Nonnull _nickName;    //屬性nickName對應的成員變量
};
// @property (nonatomic, copy)NSString *name;
// @property (nonatomic, copy)NSString *nickName;
/* @end */

從編譯之后的文件找到WJPerson_IMPL测砂,可以看出WJPerson類實際上是一個結構體茵烈;由此可見,在OC中類的本質就是結構體(struct)砌些。

3.1 來看結構體中的成員變量_name和_nickName呜投,其實就是WJPerson頭文件中聲明的兩個屬性name和nickName所對應的。
@interface WJPerson : NSObject

@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *nickName;

@end
3.2 再來看結構體NSObject_IMPL是什么呢存璃?

在WJPerson.cpp中搜索NSObject_IMPL仑荐,發(fā)現(xiàn)其實就是isa指針,

struct NSObject_IMPL {
    Class isa;
};

在OC中基本上所有的對象都是繼承NSObject纵东,但是真正的底層實現(xiàn)是objc_object的結構體類型

3.3 對象的本質拓展

在WJPerson.cpp文件中全局搜索*Class時粘招,發(fā)現(xiàn)有這樣幾行代碼如下:

typedef struct objc_class *Class;

struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

typedef struct objc_object *id;

typedef struct objc_selector *SEL;

源碼分析:
常用的id原來是也是一個objc_object結構體指針別稱,這就解釋了id修飾變量和作為返回值的時候為什么不加*了偎球,此外還有Class洒扎、SEL等也是結構體指針。

三衰絮、nonPointerIsa分析

對象alloc流程核心三步曲

a.instanceSize袍冷,計算開辟內存需要的大小。
b.calloc猫牡,向系統(tǒng)申請開辟內存胡诗,返回地址指針。
c.initInstanceIsa,初始化指針和類關聯(lián)起來煌恢。

這里主要介紹initInstanceIsa流程骇陈,見objc源碼對應處如下:
4761623767547_.pic_hd.jpg
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

進入initIsa方法流程

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 {
      ......
    }
    isa = newisa;
}

進入isa_t,發(fā)現(xiàn)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_BITFIELD分析

在表現(xiàn)一個類的地址時缩歪,會出現(xiàn)一個詞:nonPointerIsa。就比如一個類谍憔,也就可以作為一個指針,類上面是可以有很多內容是能夠被存儲的主籍。類的指針是8字節(jié),8字節(jié) * 8 bit = 64 bit(64位)。那么如果只是用來存儲一個指針绑莺,就會造成大大的浪費蔓姚,因為每個類都有一個isa指針。蘋果就對這個isa做了優(yōu)化幸海,就把和類息息相關的一些內容存在里面祟身,比如:是否正在釋放、引用計數物独、weak袜硫、關聯(lián)對象、析構函數等等(所以挡篓,OC在底層婉陷,就是C++,像OC的釋放官研,并不是真正的釋放秽澳,而是其下層的C++釋放,才是真正的釋放),這些都和類先關戏羽,所以担神,可以把這些內容存儲到那64位里面去。那么就出現(xiàn)了nonPointerIsa始花。nonPointerIsa也不是一個簡單的地址妄讯。我們可以通過查看isa_t的位域,來了解里面存的是什么衙荐。

在arm64中:

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     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
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

字段說明如下:
nonpointer:
表示是否對isa指針開啟指針優(yōu)化 0:純isa指針捞挥,1:不?是類對象地址,isa中包含了類信息、對象的引?計數等忧吟;

has_assoc:
關聯(lián)對象標志位砌函,0沒有,1存在;

has_cxx_dtor:
該對象是否有C++或者Objc的析構器,如果有析構函數,則需要做析構邏輯,如果沒有,則可以更快的釋放對象讹俊;

shiftcls:
存儲類指針的值垦沉,開啟指針優(yōu)化的情況下,在arm64架構中有33位?來存儲類指針仍劈;

magic:
?于調試器判斷當前對象是真的對象還是沒有初始化的空間厕倍;
weakly_referenced:志對象是否被指向或者曾經指向?個ARC的弱變量,沒有弱引?的對象可以更快釋放贩疙;

deallocating:
標志對象是否正在釋放內存讹弯;

has_sidetable_rc:
當對象引?技術?于10時,則需要借?該變量存儲進位这溅;

extra_rc:
當表示該對象的引?計數值组民,實際上是引?計數值減1,例如悲靴,如果對象的引?計數為10臭胜,那么extra_rc為9。如果引?計數?于10癞尚,則需要使?到下?的has_sidetable_rc耸三。

以arm64為例,堆中浇揩,8字節(jié)對齊排列仪壮,8字節(jié) * 8 bit = 64 bit(64位)。
那么胳徽,nonpointer占[1]號位置睛驳,has_assoc占[2]號位置,has_cxx_dtor占[3]號位置膜廊,shiftcls占[4 ~ 36]號位置乏沸,magic占[37~42]號位置,weakly_referenced占[43]號位置爪瓜,deallocating占[44]號位置蹬跃,has_sidetable_rc占[45]號位置,extra_rc占[46 ~ 64]號位置铆铆。

總結:
1.OC中類的本質就是結構體蝶缀。
2.ISA是通過共用體(聯(lián)合體)互斥的特性,確定ISA是純的ISA還是NONPOINTER_ISA,并通過位域來實現(xiàn)節(jié)約空間薄货。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末翁都,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谅猾,更是在濱河造成了極大的恐慌柄慰,老刑警劉巖鳍悠,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坐搔,居然都是意外死亡藏研,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門概行,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蠢挡,“玉大人,你說我怎么就攤上這事凳忙∫堤ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵涧卵,是天一觀的道長堡称。 經常有香客問我,道長艺演,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任桐臊,我火速辦了婚禮胎撤,結果婚禮上,老公的妹妹穿的比我還像新娘断凶。我一直安慰自己伤提,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布认烁。 她就那樣靜靜地躺著肿男,像睡著了一般。 火紅的嫁衣襯著肌膚如雪却嗡。 梳的紋絲不亂的頭發(fā)上舶沛,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音窗价,去河邊找鬼如庭。 笑死,一個胖子當著我的面吹牛撼港,可吹牛的內容都是我干的坪它。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼帝牡,長吁一口氣:“原來是場噩夢啊……” “哼往毡!你這毒婦竟也來了?” 一聲冷哼從身側響起靶溜,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤开瞭,失蹤者是張志新(化名)和其女友劉穎懒震,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體惩阶,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡挎狸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了断楷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锨匆。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖冬筒,靈堂內的尸體忽然破棺而出恐锣,到底是詐尸還是另有隱情,我是刑警寧澤舞痰,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布土榴,位于F島的核電站,受9級特大地震影響响牛,放射性物質發(fā)生泄漏玷禽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一呀打、第九天 我趴在偏房一處隱蔽的房頂上張望矢赁。 院中可真熱鬧,春花似錦贬丛、人聲如沸撩银。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽额获。三九已至,卻和暖如春恭应,著一層夾襖步出監(jiān)牢的瞬間抄邀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工昼榛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撤摸,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓褒纲,卻偏偏與公主長得像准夷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子莺掠,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容