【詳解】探究 Objective-C 對(duì)象的底層原理

本文基于對(duì)象的實(shí)現(xiàn)原理來(lái)深入剖析 OC 的底層相關(guān)原理。這里并不會(huì)簡(jiǎn)單的介紹純理論知識(shí),而是借助工具和編碼實(shí)現(xiàn)相關(guān)業(yè)務(wù)邏輯并作論述。

內(nèi)容簡(jiǎn)介

1、instance對(duì)象的內(nèi)存探究
2、OC對(duì)象的分類及其底層數(shù)據(jù)結(jié)構(gòu)
3、isa/superclass指針
4、OC對(duì)象相關(guān)總結(jié)

一、instance對(duì)象的內(nèi)存探究

我們平時(shí)創(chuàng)建一個(gè) OC 對(duì)象是這樣的:NSObject *obj = [[NSObject alloc] init];但是我們這樣編寫一行代碼之后,不妨思考下:它最終會(huì)生成什么樣子的代碼?obj 對(duì)象的內(nèi)存分配是怎樣的?對(duì)象底層的數(shù)據(jù)結(jié)構(gòu)是如何的?帶著這些疑問(wèn),我們可以一探究竟。

眾所周知:OC 在編譯器的作用下,最終會(huì)轉(zhuǎn)成 C/C++ 代碼,進(jìn)而轉(zhuǎn)成匯編代碼,最后才會(huì)生成機(jī)器可以識(shí)別的二進(jìn)制代碼,如下圖:

因此,作為 iOS 工程師理論上我們可以通過(guò)C/C++、匯編、機(jī)器語(yǔ)言來(lái)探究它的底層。但由于篇幅原因,本文會(huì)重點(diǎn)從 C/C++ 層面來(lái)一一論述。

要想看 obj 的底層實(shí)現(xiàn),我們需要借助 clang(Xcode自帶的編譯器前端) 編譯器進(jìn)行編譯?;诤?jiǎn)單考量,我們可以建立一個(gè)命令行項(xiàng)目,然后 cd 到 main.m 的目錄下,通過(guò)終端運(yùn)行指令:

  • $: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp

具體操作可參考下圖:

此時(shí)生成的 main_arm64.cpp 就是 main.m 在編譯后的源碼文件。通過(guò)查閱源碼發(fā)現(xiàn):NSObject 轉(zhuǎn)成 C++ 代碼長(zhǎng)這樣子的:

二者極其的相似,而 Class 又是這樣定義的: typedef struct objc_class Class,說(shuō)白了它就是一個(gè)結(jié)構(gòu)體!因此說(shuō),一個(gè) NSObject 類編譯后是一個(gè) C++ 的結(jié)構(gòu)體,結(jié)構(gòu)體的成員變量?jī)H包含一個(gè) isa。因此我們可以得出結(jié)論:一個(gè) NSObject 對(duì)象在內(nèi)存分配上相當(dāng)于包含一個(gè)成員變量的結(jié)構(gòu)體的內(nèi)存分配。而該結(jié)構(gòu)體在64bit 系統(tǒng)下只占用8個(gè)字節(jié)(當(dāng)然32bit下占用4個(gè)字節(jié)),也就是說(shuō) obj 對(duì)象在內(nèi)存分配上實(shí)際占用8個(gè)字節(jié)。

我們不妨繼續(xù)深入探究來(lái)驗(yàn)證上述結(jié)論。通過(guò) runtime 的 class_getInstanceSize(Class _Nullable cls) 我們可以獲取 NSObject 的實(shí)例對(duì)象所占用內(nèi)存的大?。煌ㄟ^(guò) *extern size_t malloc_size(const void ptr) 可以獲得 obj 指針?biāo)赶虻哪菈K內(nèi)存的大小。但是,二者有什么區(qū)別嗎?我們可以通過(guò) Xcode 日志查看一下,如圖:

打印結(jié)果不一樣!為什么?帶著這個(gè)好奇心我們不妨通過(guò)兩個(gè)函數(shù)的具體實(shí)現(xiàn)來(lái)解釋說(shuō)明。

實(shí)際上,目前來(lái)說(shuō)蘋果的很多底層實(shí)現(xiàn)都是開(kāi)源的了。我們可以在蘋果開(kāi)源上來(lái)下載源碼閱讀,本文內(nèi)容中只需下載 objc4 即可。查閱源碼后不難發(fā)現(xiàn):class_getInstanceSize 的底層實(shí)現(xiàn)其實(shí)就是依次調(diào)用了:alignedInstanceSize()、word-align()。我們都知道 alloc 的內(nèi)部實(shí)際是調(diào)用了allocWithZone:,而通過(guò)源碼發(fā)現(xiàn) allocWithZone 內(nèi)部又是依次調(diào)用了:objc-rootAllocWithZone > class-createInstance > class-createInstanceFromZone > instanceSize > alignedInstanceSize > word-align。仔細(xì)看可以發(fā)現(xiàn),最后兩步的函數(shù)調(diào)用二者是一樣的。但是關(guān)鍵一步在于:instanceSize()。如圖:

原來(lái)蘋果在 CF 框架內(nèi)部硬性規(guī)定了所有的對(duì)象在內(nèi)存上必須至少是占用16個(gè)字節(jié)。也就是說(shuō):alignedInstanceSize() 內(nèi)存對(duì)齊后是8個(gè)字節(jié),由于extraBytes等于0,因此 size < 16成立,所以最終的 size 返回的是16!

綜上:也就解釋了為什么打印了 8 和 16 不同的結(jié)果。實(shí)際上,我們可以這么理解class_getInstanceSize、mallocsize的區(qū)別:前者是獲得NSObject實(shí)例對(duì)象的成員變量所占用的大小,后者是操作系統(tǒng)實(shí)際上給NSObject的實(shí)例對(duì)象 obj 分配的內(nèi)存大小。打個(gè)比方:我今天去菜市場(chǎng)買肉,我本來(lái)只要半斤就夠吃了,但是賣肉的老板必須賣給我一斤,因?yàn)槔习遒u肉的規(guī)則就是至少是每人每次賣一斤,且必須也是一斤的整數(shù)倍量。

假如現(xiàn)在我定義一個(gè) Person 類,其內(nèi)部包含一個(gè)成員變量:int age; 那么我們猜一下class_getInstanceSize、mallocsize分別會(huì)打印多少?答案是:16、16。沒(méi)錯(cuò)的,因?yàn)檫@里涉及到一個(gè)結(jié)構(gòu)體成員數(shù)據(jù)對(duì)齊的常識(shí)。即在結(jié)構(gòu)體中,成員數(shù)據(jù)對(duì)齊需滿足以下規(guī)則:

  • 結(jié)構(gòu)體中的第一個(gè)成員的首地址也即是結(jié)構(gòu)體變量的首地址。
  • 結(jié)構(gòu)體中的每一個(gè)成員的首地址相對(duì)于結(jié)構(gòu)體的首地址的偏移量(offset)是該成員數(shù)據(jù)類型大小的整數(shù)倍。
  • 結(jié)構(gòu)體的總大小是對(duì)齊模數(shù)(對(duì)齊模數(shù)等于#pragma pack(n)所指定的n與結(jié)構(gòu)體中最大數(shù)據(jù)類型的成員大小的最小值)的整數(shù)倍。

因此,在包含一個(gè)成員變量的 Person 類中,編譯后生成的 C++ 結(jié)構(gòu)體中本質(zhì)上是有兩個(gè)成員:isa、age,由于 isa 占用8個(gè)字節(jié),age 類型為 int 占用4個(gè)字節(jié),為了滿足規(guī)則第三條:結(jié)構(gòu)體的總大小必須是最大數(shù)據(jù)類型的成員大小的整數(shù)倍,就是 8 的整數(shù)倍為 16。

綜上,一個(gè) OC 對(duì)象在編譯后會(huì)生成一個(gè) C++ 結(jié)構(gòu)體,結(jié)構(gòu)體中包含了所有的成員變量和一個(gè) isa,在內(nèi)存分配上會(huì)按照一定的對(duì)齊規(guī)則進(jìn)行管理。

二、OC對(duì)象的分類及其底層數(shù)據(jù)結(jié)構(gòu)

上面講述了 instance 對(duì)象本質(zhì)的一些認(rèn)識(shí),接下來(lái)重點(diǎn)闡述 OC 三大對(duì)象的底層之間的相互關(guān)系。
從語(yǔ)言設(shè)計(jì)角度來(lái)劃分,可將 OC 對(duì)象分為三大類:

  • 實(shí)例對(duì)象,
  • 類對(duì)象
  • 元類對(duì)象

1、如何獲取三大對(duì)象的地址?

首先導(dǎo)入頭文件#import <objc/runtime.h>并創(chuàng)建以下對(duì)象:

obj1、obj2為兩個(gè)不同的實(shí)例對(duì)象
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];

objClass1、objClass2、objClass3、objClass4為NSObject的類對(duì)象
Class objClass1 = [obj1 class];
Class objClass2 = [obj2 class];
Class objClass3 = object_getClass(obj1);
Class objClass4 = object_getClass(obj2);

objMetaClass1、objMetaClass2為NSObject的元類對(duì)象
Class objMetaClass1 = object_getClass(objClass2);
Class objMetaClass2 = object_getClass(objClass4);

通過(guò)日志打印,獲得地址分別如下圖所示:

通過(guò)閱讀內(nèi)存地址可知:
一個(gè)類可以創(chuàng)建多個(gè)不同的實(shí)例對(duì)象,但是僅可以創(chuàng)建一個(gè)類對(duì)象和一個(gè)元類對(duì)象!

2、對(duì)象的底層數(shù)據(jù)結(jié)構(gòu)

今假設(shè)存在以下三種類JDMan、JDPerson、NSObject, 繼承關(guān)系為:
JDMan繼承自JDPerson,JDPerson繼承自NSObject。
且JDPerson包含2個(gè)成員變量、1個(gè)屬性、1個(gè)對(duì)象方法,1個(gè)類方法;JDMan同JDPerson
如圖所示:

結(jié)合前邊討論,我們可將OC的類編譯成C++代碼,如下圖所示:

不難發(fā)現(xiàn):所有的實(shí)例對(duì)象的C++結(jié)構(gòu)體中僅僅包含了成員變量(當(dāng)然也存儲(chǔ)這一個(gè)isa指針和一個(gè)superclass指針),也就是說(shuō)實(shí)例對(duì)象僅僅存儲(chǔ)各自的成員變量的值。那么他們的對(duì)象方法、類方法、甚至協(xié)議等相關(guān)信息存儲(chǔ)在哪里呢?

我們不妨先來(lái)思考一個(gè)事情:對(duì)象可以創(chuàng)建多個(gè),每個(gè)對(duì)象都有自己的成員變量和對(duì)應(yīng)的值,但是方法大家調(diào)用的都是同一個(gè),不管你是實(shí)例對(duì)象還是類對(duì)象都是調(diào)用的一個(gè)方法。因此OC在設(shè)計(jì)這門語(yǔ)言的時(shí)候,我們有必要將只需要存儲(chǔ)一份的數(shù)據(jù)交給實(shí)例對(duì)象去管理嗎?肯定不需要。

上述我們發(fā)現(xiàn)而類對(duì)象和元類對(duì)象正好在內(nèi)存中只有一份。這恰巧在某種程度上佐證了一個(gè)事實(shí):實(shí)例方法存儲(chǔ)中類對(duì)象中,類方法存儲(chǔ)在元類對(duì)象中。

當(dāng)然如果進(jìn)一步分析的話,類對(duì)象中都存儲(chǔ)著以下信息數(shù)據(jù):

isa指針
superclass指針
類的成員變量信息(ivar)
類的屬性信息(@property)、
類的協(xié)議信息(@protocol)、
類的對(duì)象方法信息(instance method)、
......

而元類對(duì)象中存儲(chǔ)的信息數(shù)據(jù)包括:

isa指針
superclass指針
類的類方法信息(class method)
......

上述只是我們主觀的分析得出的結(jié)論。那么接下來(lái)我們就要去用事實(shí)證明這些結(jié)論的正確性。

我們已經(jīng)知道,三大對(duì)象(實(shí)例對(duì)象、類對(duì)象、元類對(duì)象)本質(zhì)上都是OC中的Class類型的結(jié)構(gòu)體。要想證明我們上述的分析結(jié)果,那么就必須要徹底探究清楚Class的深層結(jié)構(gòu)。在OC中,我們通過(guò)查看頭文件的方式只能看到typedef struct objc_class *;這樣的聲明,繼續(xù)閱讀objc_class *的相關(guān)代碼。如下圖所示:

但是很遺憾,在OC2.0版本中很多都是過(guò)期的,如此、這并不是我們期望的。

那么有沒(méi)有其他方式呢?答案是有的。

幸好蘋果給我們開(kāi)源了相關(guān)源碼。我們可以到蘋果開(kāi)源上去下載相關(guān)源碼objc4-723庫(kù),從objc4-723庫(kù)中找到objc-runtime-new文件進(jìn)而找到struct objc_class *的定義。如下圖所示:

會(huì)發(fā)現(xiàn)實(shí)現(xiàn)的相關(guān)代碼(上圖中并沒(méi)有展示全部的相關(guān)代碼,只是截取了部分核心代碼。如有興趣可自行查閱原文)太多了,好復(fù)雜。不過(guò)這足以能夠幫我們?nèi)プC明一些事情了。

熟悉C++的同學(xué)應(yīng)該都知道,只要是結(jié)構(gòu)體的內(nèi)部數(shù)據(jù)結(jié)構(gòu)、格式是一致的,那么就可以進(jìn)行結(jié)構(gòu)體之間的類型轉(zhuǎn)換(可以理解為OC中的對(duì)象類型強(qiáng)轉(zhuǎn),不過(guò)可能會(huì)導(dǎo)致被轉(zhuǎn)換的結(jié)構(gòu)體的某些數(shù)據(jù)的丟失)的。

基于這一點(diǎn),我們不妨把官方的實(shí)現(xiàn)進(jìn)行簡(jiǎn)化:只保留必要的我們期望的信息,如方法緩存表、協(xié)議緩存表、屬性表、屬性信息、協(xié)議信息、描述信息等等。如下所示:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;             // instance對(duì)象占用的內(nèi)存空間
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t *ivarLayout;
    const char *name;                  // 類名
    method_list_t *baseMethodList;
    protocol_list_t *baseProtocols;
    const ivar_list_t *ivars;          // 成員變量列表
    const uint8_t *weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t *methods;             // 方法列表
    property_list_t *properties;        // 屬性列表
    const protocol_list_t *protocols;   // 協(xié)議列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t *data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/// OC對(duì)象
struct jd_objc_object {
    void *isa;
};

/// 類對(duì)象
struct jd_objc_class : jd_objc_object {
    Class superclass;
    cache_t cache;  
    class_data_bits_t bits;
public:
    class_rw_t *data() {
        return bits.data();
    }
    
    jd_objc_class *metaClass() {
        return (jd_objc_class *)((long long)isa & ISA_MASK);
    }
};

這樣我們就可以通過(guò)斷點(diǎn)調(diào)試的方式進(jìn)行驗(yàn)證了。以類對(duì)象personClassData為例進(jìn)行驗(yàn)證。因?yàn)?code>struct jd_objc_class *中有isa(繼承而來(lái))、superclass、cache(方法緩存相關(guān))、bits、data等信息,通過(guò)分析可得:通過(guò)data函數(shù)的調(diào)用可以獲得類對(duì)象中主要的存儲(chǔ)的信息數(shù)據(jù)。personClassData正是通過(guò)調(diào)用struct jd_objc_class *data()函數(shù)獲取的struct class_rw_t *類型的返回值。如下圖對(duì)struct class_rw_t *的分析所示:

從圖中可知:personClass類對(duì)象中的data函數(shù)返回的personClassData(struct class_rw_t *類型)存儲(chǔ)著很多和JDPerson相關(guān)的信息:如成員變量、協(xié)議信息、對(duì)象方法等等。這樣就證明了我們前邊的分析的結(jié)論了。

同理:我們可以對(duì)元類對(duì)象也用同樣的方式進(jìn)行分析。結(jié)果是一樣的。

三、isa/superclass指針

我們已經(jīng)知道,三大對(duì)象的結(jié)構(gòu)體中都有一個(gè)isa指針、一個(gè)superclass指針。那么他們之間的關(guān)系是如何的呢?

1、isa指針

我們可以通過(guò)代碼進(jìn)行測(cè)試,如下圖:

obj是一個(gè)實(shí)例對(duì)象,里邊有一個(gè)isa指針,再用LLVM的相關(guān)指令:p/x可以打印出obj的isa的值0x001dffffa8f48141。objClass為Class類型的類對(duì)象,將其強(qiáng)制轉(zhuǎn)換成我們簡(jiǎn)化后的結(jié)構(gòu)體struct jd_objc_class *然后打印的其地址為0x001dffffa8f48140。地址并不相等。貌似實(shí)例對(duì)象的isa指針不是指向類對(duì)象。

然而事實(shí)并不是這樣的。從iOS系統(tǒng)支持64bit以來(lái),實(shí)例對(duì)象的isa指針需要進(jìn)行一個(gè)與運(yùn)算& ISA_MASK才行。也就是說(shuō)用0x001dffffa8f48141 & ISA_MASK 得到的值才是isa真實(shí)的指向。那么對(duì)于ISA_MASK的定義蘋果是這樣設(shè)計(jì)的:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

由于我們Mac(iOS架構(gòu)是arm64)的架構(gòu)是__x86_64__,因此也就是說(shuō):我們要想得到實(shí)例對(duì)象isa真正的指向我們需要進(jìn)行運(yùn)算:0x001dffffa8f48141 & 0x00007ffffffffff8。得到的結(jié)果是:0x001dffffa8f48140。正如上圖所示的一樣。因此實(shí)例對(duì)象的isa指針事實(shí)上是指向類對(duì)象的。

那么類對(duì)象的isa指針是指向哪里的呢?看下圖代碼所示:

jd_manClass是我們獲得簡(jiǎn)化后的類對(duì)象,jd_manMetaClass是我們獲得簡(jiǎn)化后的元類對(duì)象。圖中代碼同理:我們也驗(yàn)證了類對(duì)象的isa指針指向的是元類對(duì)象。

2、superclass

我們已經(jīng)驗(yàn)證到:實(shí)例對(duì)象的isa指向類對(duì)象,類對(duì)象的isa指向元類對(duì)象。但是他們各自的superclass又是如何指向的呢?
其實(shí),我們可以利用類似的斷點(diǎn)調(diào)試方式對(duì)superclass進(jìn)行類似的探究。
我們同樣可利用JDMan、JDPerson、NSObjet來(lái)測(cè)試。代碼如下:

// JDMan類對(duì)象結(jié)構(gòu)
Class manClass = [JDMan class];
struct jd_objc_class *jd_manClass = (__bridge struct jd_objc_class *)manClass;
// JDPerson類對(duì)象結(jié)構(gòu)
Class personClass = [JDPerson class];
struct jd_objc_class *jd_personClass = (__bridge struct jd_objc_class *)personClass;
// NSObject類對(duì)象結(jié)構(gòu)
Class objClass = [NSObject class];
struct jd_objc_class *jd_objClass = (__bridge struct jd_objc_class *)objClass;

相關(guān)的日志打印如下:

(lldb) p/x jd_manClass -> superclass
(Class) $0 = 0x0000000100001460 JDPerson
(lldb) p/x jd_personClass 
(jd_objc_class *) $1 = 0x0000000100001460
(lldb) p/x jd_personClass -> superclass
(Class) $2 = 0x00007fffa8f48140 NSObject
(lldb) p/x jd_objClass 
(jd_objc_class *) $3 = 0x00007fffa8f48140
(lldb) 

很明顯:子類類對(duì)象的superclass指向父類類對(duì)象,父類類對(duì)象的superclass指向基類(即NSObject類)

四、OC對(duì)象相關(guān)總結(jié)

說(shuō)了這么多,現(xiàn)以下圖總結(jié):


1、在OC對(duì)象中可分為實(shí)例對(duì)象、類對(duì)象、元類對(duì)象

2、實(shí)例對(duì)象保存成員變量信息,類對(duì)象保存屬性、對(duì)象方法、協(xié)議信息、成員變量描述信息,元類對(duì)象保存的是類方法等信息

3、對(duì)于一個(gè)NSObject對(duì)象,在內(nèi)存分配時(shí)操作系統(tǒng)給其分類了16個(gè)字節(jié)(通過(guò)malloc_size獲得),但是實(shí)際使用的是8個(gè)字節(jié)(64bit環(huán)境下通過(guò)class_getInstanceSize獲得)

**4、類對(duì)象和元類對(duì)象本質(zhì)上都是一個(gè)Class類型的結(jié)構(gòu)體,Class的定義為:typedef struct objc_class Class; 實(shí)例對(duì)象的本質(zhì)是objc_object類型的結(jié)構(gòu)體。另外objc_class繼承自objc_object,而typedef struct objc_class Class的具體實(shí)現(xiàn)可去蘋果開(kāi)源下載源碼,可參考o(jì)bjc4-723庫(kù)中objc-runtime-new.h文件中的相關(guān)源碼實(shí)現(xiàn)

5、isa:子類的實(shí)例對(duì)象的isa指針指向子類的類對(duì)象,子類的類對(duì)象的isa指針指向子類的元類對(duì)象,子類的元類對(duì)象的isa指針指向基類的元類對(duì)象(即NSObject的元類對(duì)象)

6、superclass:子類的類對(duì)象的superclass指針指向父類的類對(duì)象,父類的類對(duì)象的superclass指針指向基類的類對(duì)象,基類的類對(duì)象的superclass指針指向nil(即沒(méi)有父類指向nil);元類對(duì)象的superclass同理:子類的元類對(duì)象的superclass指向父類的元類對(duì)象,父類的元類對(duì)象的superclass指向基類的元類對(duì)象,基類的元類對(duì)象的superclass指向基類的類對(duì)象

寫在最后:

本文對(duì)OC中三大對(duì)象(實(shí)例對(duì)象、類對(duì)象、元類對(duì)象)之間的聯(lián)系和區(qū)別以及我們開(kāi)發(fā)中常見(jiàn)的isa、superclass等指針進(jìn)行了較為詳盡的論述。同時(shí)我們一步一步的也窺探了OC實(shí)例對(duì)象在內(nèi)存中的分配情況。本文涉及到的內(nèi)容在一些常規(guī)開(kāi)發(fā)中可能并不常見(jiàn),但是也許能很好的幫助我們更深入的去理解OC的一些底層的實(shí)現(xiàn)原理。這樣我們?cè)陂_(kāi)發(fā)中遇到一些莫名其妙的問(wèn)題時(shí)也許本文就起作用了,同時(shí)也希望通過(guò)本文的闡述,能給讀者一些啟發(fā),能幫助大家提高閱讀源碼的主動(dòng)性,促進(jìn)大家勇于去探究未知事情的本質(zhì)。由于筆者水平有限,如有紕漏煩請(qǐng)大家積極斧正,歡迎評(píng)論區(qū)留言;如果您喜歡這篇文章并覺(jué)得對(duì)您有幫助,請(qǐng)別忘了STAR收藏一下哈。讓我們共同學(xué)習(xí),共同成長(zhǎng)。謝謝大家的支持!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容