想成爲一名優秀的iOS開發者,對底層的原理學習是必不可少的,筆者整理了一系列有關OC的底層文章,但願能夠幫助到你。這篇文章主要講解的是類的底層結構分析。編程
2.iOS的OC對象的內存對齊緩存
3.iOS的OC的isa的底層原理bash
開始介紹類的結構以前,請問一下,你在開發的過程當中有沒有想過一個問題。就是建立多個相同類型的對象的時候,那麼這個對象的類是否是多個呢?帶着這個問題,有了以下不一樣形式獲取到類的代碼markdown
Class class1 = [TestJason class]; Class class2 = [TestJason alloc].class; Class class3 = object_getClass([TestJason alloc]); NSLog(@"%p====%p===%p",class1,class2,class3); ===========運行的結果=========== LGTest[1541:33345] 0x100002848====0x100002848===0x100002848 複製代碼
從上面的運行結果能夠知道,類在內存裏面只存在一份。less
仍是使用蘋果的objc4-756.2
源碼來學習的,具體能夠看iOS的OC對象建立的alloc原理這篇文章有介紹。經過Class
的源碼post
/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; struct objc_class : objc_object { // 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 class_rw_t *data() { return bits.data(); } void setData(class_rw_t *newData) { bits.setData(newData); } .... /// Represents an instance of a class. struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; 複製代碼
從源碼能夠知道Class
是一個objc_class
的結構體,而objc_class
是繼承自objc_object
的,由於在面向對象編程中萬物皆對象,從這裏也能夠知道,其實類也是對象的。從中能夠知道類裏面分別有從父類繼承的isa
,superclass
,cache_t
和class_data_bits_t類型的bits
。由於類
在底層中最終是編譯成結構體的形式,因此你是否是很好奇大部分的基類NSObject
在底層是怎樣的形式呢?學習
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ROOT_CLASS OBJC_EXPORT @interface NSObject <NSObject> { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop } 複製代碼
經過源碼能夠知道,NSObject
的底層是與objc_object
是同樣的。在平常開發中,定義類的時候都是會有屬性,變量和方法的,你是否是很好奇屬性,成員變量和方法在類的底層中儲存在哪裏?接下來,我都會一一介紹。ui
爲了方便介紹接下來的內容定義了一個TestObject
的類,下面是這個類的定義和實現,而且經過lldb
的指令來介紹。this
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface TestObject : NSObject{ NSString *nickName; } @property(nonatomic,copy) NSString *name; -(void)sayName; +(void)sayNickName; @end NS_ASSUME_NONNULL_END //實現的代碼 TestObject *testObject = [TestObject alloc]; Class tClass = object_getClass(testObject); NSLog(@"%@===%p",testObject,tClass); 複製代碼
經過lldb
的指令獲得以下:
0x001d800100001659
是
isa
的內存值,
0x0000000100b37140
是
superclass
的,
0x00000001003da290
是
cache_t
的,那麼
0x0000000000000000
就是
bits
了嗎?這幾個值從字面意思能夠知道
superclass
是存放父類的,
cache_t
是存放一些緩存的東西(這塊內存後續會出一篇文章介紹),那麼
bits
應該就是存放咱們須要的屬性和實例方法的了。下面是
class_rw_t
的源碼
class_rw_t *data() { return bits.data(); } struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; 複製代碼
從上面的lldb
中打印出來的0x0000000000000000
是0的這樣的話就是否是說直接打印不出來呢?並非的,咱們能夠經過分析類裏面的各個屬性佔的字節大小而後經過內存偏移來找到最終bits
的內存值。
經過源碼能夠知道objc_class
的內部定義
struct objc_class : objc_object {
// 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
···
}
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
···
}
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
複製代碼
經過前面的文章能夠知道,isa
是一個聯合體(union)佔8個字節,superclass
由於是一個類因此也佔8個字節,由於cache
是一個結構體類型(struct
),這個大小是根據它裏面的屬性的容量來決定大小的,由於_buckets
是一個結構體指針
佔8字節,mask_t
經過源碼能夠知道佔4字節,因此cache
共佔16字節。由isa
到cache
一共是32字節,轉爲16進制就是0x20
,由上圖能夠知道,tClass
類的內存值的起始位置是0x100001680
經過內存偏移0x20
能夠獲得0x1000016a0
,那麼這個值就是bits
的內存值。也能夠直接用x/5gx tClass
來打印出來,獲得的內存值也是0x1000016a0
bits
裏面的
data()
能夠獲得
class_rw_t
,而
class_rw_t
能夠獲得屬性的值,因此經過
lldb
的指令能夠打印
class_rw_t
裏面所指向的值。如下是內存偏移到
0x1000016a0
打印出來的結果
因此屬性是放在
class_rw_t
的
properties
裏面,能夠經過
properties
裏面的
list
獲得
property_list_t
的數組,最終打印了屬性
name
可是再用
$7
這個數組來找的時候,發現是找不到
TestObject
類的
nickName
這個成員變量的,這時候能夠看一下
class_rw_t
裏面的
const class_ro_t *ro
。
經過源碼能夠知道class_ro_t
的各個值
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #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; method_list_t *baseMethods() const { return baseMethodList; } }; 複製代碼
這個ro
裏面也有method_list_t
,protocol_list_t
和property_list_t
,其中多了ivar_list_t
。經過lldb
的指令最終找到了成員變量nickName
。
TestObject
中有成員變量的數量爲2個,其中第一個是
nickName
,這時候就有點奇怪了,咱們不是隻在
TestObject
中只定義了一個
nickName
這個成員變量嗎?多出來的一個是什麼呢?原來在類中定義的屬性也是會被定義爲帶下劃線的成員變量的,經過
lldb
能夠看到
而且在rw
中的method_array_t
和ro
中的method_list_t
均可以經過獲得TestObject
的方法,以下是經過lldb
的指令來獲取rw
中的方法
從中能夠知道類TestObject
有方法數爲4個,而且第一個的方法是cxx_destruct
,另外的兩個方法會不會是咱們在類中定義的實例方法sayName
和類方法sayNickName
,那麼仍是多出來了一個方法數量,經過lldb
打印出來的
其實就是定義的屬性name
的getter和setter方法的兩個方法和sayName
的實例方法以及一個系統的cxx_destruct
方法,並無sayNickName
這個類方法。這是爲何呢?
注意:若是實例方法只是在類的`.h`文件聲明瞭,可是並無在`.m`文件中實現的話,是不在`rw`和`ro`裏面的
思考一個問題:爲何成員變量會存在
class_ro_t
裏面不存在class_rw_t
裏面呢?class_rw_t
與class_ro_t
有什麼不同呢?
經過上面的能夠知道類方法是不存在類裏面的,是存在元類
裏面的,能夠經過lldb
指令來查找,先找到當前的類的isa
,再經過isa
&ISA_MASK
能夠獲得元類
。
0x0000000100002700
就是
元類
的內存值,再經過
x/5gx
能夠獲得
元類
中的
class_data_bits_t
,此時就至關於走一遍上面介紹過的查找
bits
裏面的方法的流程
最終再元類中的
bits
的方法中能夠查找到
sayNickName
這個類方法。
從上面的介紹能夠知道類在底層中的結構分別是isa
,superclass
,cache_t
和bits
,其中它們所佔的大小是不同的。bits
裏面的rw
有保存着實例方法
和屬性
,可是並無包含成員變量
,rw
中包含着ro
。ro
中保存着實例方法
,屬性
和成員變量
。成員變量
和屬性
都存在ivar
裏面,而且屬性
是如下劃線_
的形式存在的。類的類方法是存在元類的bits
的方法裏面。至此,有關類的結構分析就介紹完畢了。