本文是Objective-C系列的第8篇,主要講述OC對象的底層結構,以及分類:實例對象、類對象、元類對象。git
在上一篇Objective-C(七)對象內存分析分析後,咱們得知了一個類在內存中的存儲。github
可是,咱們只分析了類的成員變量和屬性,咱們知道,一個OC對象,還包括方法、協議等極其重要的信息,那麼它們在哪裏,又是如何存儲,如何用的呢?數組
本篇在此進一步分析Objective-C類體系的分類及其在內存中的完整分佈。bash
爲此,咱們先要進行一些準備工做。app
Objective-C中的對象,簡稱OC對象,主要能夠分爲3種:佈局
instance對象(實例對象)post
class對象(類對象)測試
meta-class對象(元類對象)ui
咱們針對OC中對象分爲三種進行了測試,測試結果以下:this
從上圖咱們能夠獲得如下結論:
那麼測試代碼中的幾個方法,下面也簡要說明下,部分截取源碼項目-BFOCClass分類-01。
在NSObject.mm
中:
+ (Class)class {
return self;
}
複製代碼
返回類對象(元類對象)自己。
因此,[[[NSObject class] class] class]
不管調用多少次class,其返回都是NSObject class對象。
返回實例對象的類對象
- (Class)class {
return object_getClass(self);
}
複製代碼
由(2)中可知,
該方法與object_getClass從方法名看極其類似。
Class objc_getClass(const char *aClassName)
{
if (!aClassName) return Nil;
// NO unconnected, YES class handler
return look_up_class(aClassName, NO, YES);
}
複製代碼
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
複製代碼
返回傳入obj
中isa
所指向的對象,傳入的obj多是instance
對象、class
對象、meta-class
對象
其返回值對應爲:
a) 若是是instance
對象,返回class
對象
b) 若是是class
對象,返回meta-class
對象
c) 若是是meta-class
對象,返回NSObject(基類)的meta-class
對象
因此上面測試中:
object_getClass(person1):返回即class
對象;
object_getClass(personClass5):返回即meta-class
對象;
返回是否爲meta-class對象。
咱們會從源碼摘抄一部分結構體,用來進行後面的論證。
NSObject
NSObject從源碼中獲得,其第一個成員變量爲isa
,該Class
結構體第一個成員變量名稱也是isa
。
isa
裏則包含了(元)類信息的存放地址。
class_rw_t
class_rw_t
、class_ro_t
均是存放類中信息的結構體,包括成員變量、屬性列表、協議列表及方法列表,其具體以下:
目前,咱們class_ro_t
當前類在編譯期就已經肯定的屬性、方法以及遵循的協議。
class_rw_t
則是在運行時,runtime從新加載佈局的當前類的屬性、方法和協議等。
method_t
method_t
是 方法最底層的結構。method_list_t
method_t
class_ro_t
中的成員變量。method_array_t
method_t
class_rw_t
中的成員變量;三者的關係以下圖:
與method相似,property、protocol的相似。
至於爲何會有一維和二維數組,後續文章會詳述該問題。
property_t
property_array_t
與property_list_t
的結構與上面的method中的數組相似,分別是二維、一維數組。
struct property_t {
const char *name; //屬性名
const char *attributes; //字符串,代表了該屬性的特性
};
複製代碼
假如屬性名爲name,類型爲NSString,那麼對應的attributes:T@"NSString",C,N,V_name
protocol_t
protocol_array_t
、protocol_list_t
分別是存放protocol_t
的二維及一維數組。
相似的,咱們關注protocol_t
結構體:
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
const char *_demangledName;
property_list_t *_classProperties;
};
複製代碼
ivar_t
咱們發現,針對成員變量,只有ivar_list_t
,而沒有ivar_array_t
這樣的結構。
從這裏咱們能夠知道,就算是runtime,也沒法在運行時往類中添加成員變量。
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
};
複製代碼
咱們已經對NSObject實例對象的內存結構至關熟悉了。咱們再闡述如下觀點:
下面,咱們針對編譯期的isa
,結合源碼,咱們獲得以下:
運行期的isa:
上面,咱們作了一些準備工做,瞭解了NSObject如何存儲各類各樣信息的結構體及存儲在什麼地方。
下面咱們將會針對,三種類型作一個簡單的描述。
其內存佈局以下:
instance對象在內存中存儲的信息有:
isa
指針class對象在內存中存儲的信息主要包括
isa
指針superclass
指針meta-class對象和class對象的內存結構是同樣的,可是用途不同,在內存中存儲的信息主要包括
isa
指針superclass
指針isa
在平時開發中不多使用,可是相信不少iOS開發者並不陌生。
在前面瞭解了NSObject、各類結構體及對象三大分類以後,蘋果爲咱們提供了的Cocoa/Cocoa Touch中,類蔟體系的創建依賴兩個指針:isa
和superclass
。那麼又是如何串聯的?
橫向聯繫:isa
的做用就是,將某一個類的instance對象、class對象、meta-class對象創建了橫向聯繫,爲查找對象存儲各種信息提供指引。
縱向聯繫:superclass
則是面嚮對象語言中最爲重要的特性——繼承的體現,它體現的是類與類之間的聯繫,即爲類圖中創建了縱向聯繫。
請注意區分:一個類各類對象之間的聯繫,以及不一樣類之間的聯繫!
咱們爲了驗證該結論,在真機設備上,運行項目-BFOCClass分類-01,嘗試獲取了BFPerson對象,並打印類相關地址:
BFPerson instance - 0x12fd1f570 0x12fd366e0
BFPerson class - 0x100091e28 0x100091e28 0x100091e28 0x100091e28 0x100091e28 0
BFPerson meta class - 0x100091e00 1 0x100091e28 0
(lldb) x/2xw 0x12fd1f570
0x12fd1f570: 0x00091e2d 0x000001a1
複製代碼
從上面打印咱們得知:
isa
,此處爲:0x1a100091e2disa
) & 0x0000000ffffffff8(ISA_MASK
) = 0x100091e28據此,isa
和superclass
聯手織起的下面這張類圖:
isa
指向
isa
指向classisa
指向meta-classisa
指向基類的meta-classsuperclass指向
superclass
指向父類的class 若是沒有父類,superclass
指針爲nilsuperclass
指向父類的meta-class 基類的meta-class的superclass
指向基類的classisa
找到class,方法不存在,就經過superclass
找父類isa
找meta-class,方法不存在,就經過superclass
找父類代碼參考BFOCClass分類-01
繼承關係:BFStudent
->BFPerson
->NSObject
BFStudent *stu = [[BFStudent alloc] init];
[stu test];
複製代碼
BFStudent實現-(void)test;
,調用BFStudent的實現方法;
BFStudent未實現-(void)test;
,若BFPerson實現,調用BFPerson實現;
BFStudent和BFPerson均未實現,NSObject若實現,調用NSObject實現;
以上都未實現,代碼是不能直接調用[stu test];
,會編譯報錯,須要改調用方式:
((**void** (*)(**id**, **SEL**))objc_msgSend)(stu, **@selector**(test));
複製代碼
+(void)test;
,崩潰;-(void)test;
,調用-(void)test;
;繼承體現中的類,均未實現,崩潰—— -[BFStudent test]: unrecognized selector sent to instance
[BFStudent test];
複製代碼
BFStudent實現+(void)test;
,調用BFStudent的實現;
BFStudent未實現+(void)test;
,若BFPerson實現,調用BFPerson實現;
BFStudent和BFPerson均未實現,調用NSObject實現;
沒法[BFStudent test];
調用,調用方式需改成:
Class stuClass = [BFStudent class];
((void (*)(id, SEL))objc_msgSend)(stuClass, @selector(test))
複製代碼
+(void)test;
,調用+(void)test;
;-(void)test;
,調用-(void)test;
;+(void)test;
和-(void)test;
,調用+(void)test;
;繼承體現中的類,均未實現,崩潰—— +[BFStudent test]: unrecognized selector sent to instance
針對狀況1和2,大部分咱們都能理解,但有一種狀況。是有差異的。
在BFStudent和BFPerson中均未實現對應的對象方法(類方法)時,都會去NSObject中尋找。
到此,咱們針對對象已經瞭解的差很少了,可是咱們仍是沒有完整的看到運行時,對象的內部結構。
有兩種方式:
咱們採用方式二。
抽象出的結構體從這裏下載。
導入後,將編譯項目調整爲命令行項目,並將main.m改成main.mm,以支持C++編譯。
對應的項目源碼。
如今,你知道OC對象的本質了嗎?它們的分類呢?