類的結構分析
類分析初探
首先我們先創建一個類LGPerson
,再創建一個繼承于LGPerson
的子類LcrTeacher
。
@interface LGPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
-(void)sayHello;
+(void)say88;
@end
#import "LGPerson.h"
@implementation LGPerson
-(void)sayHello{
}
+(void)say88{
}
@end
#import "LGPerson.h"
@interface LcrTeacher : LGPerson
@end
#import "LcrTeacher.h"
@implementation LcrTeacher
@end
在main.m文件中我們創建兩個對象:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//ISA_MASK 0x00007ffffffffff8ULL
LGPerson *person = [LGPerson alloc];
LcrTeacher *teacher = [LcrTeacher alloc];
NSLog(@"%@ ---- %@",person,teacher);
}
return 0;
}
利用lldb
調試我們打印出下面圖片中一些信息:
如上圖所示,1
、2
處的打印結果都為LGPerson
,1
處是因為person
對象的指針指向類,所以當此對象的指針地址&ISA_MASK
時,所得到的就是LGPerson
類地址,打印即為LGPerson
。2 處打印的地址是類的isa&ISA_MASK
后得到的元類
的信息,那么這個元類
到底是什么?
元類
對象的isa
指向的是所對應的類的信息,類其實也是一個對象
,它的內存信息中保存的位域信息
所指向的就是由蘋果定義的元類
:
1.元類是由系統定義
并創建
的,這個過程是在編譯期間
完成的。
2.元類的類對象
是類
,元類中保存著類的相關類方法信息
。
3.元類是沒有自己的名字,由于與當前類相關聯
,所以蘋果直接拿當前類名命名元類。
由此,上面的1
、2
處打印的都為LGPerson
就是元類
導致的。
通過lldb可以調試元類信息鏈,即isa走向,對象--->類--->元類--->根元類--->根類(NSObject):
上圖可見:
-
對象
的isa指向類
。 -
類
的isa指向元類
。 -
元類
的isa指向根元類
。 -
根元類
的isa指向自己
。
著名isa及Superclass走位圖
結合上面的工程,將我們自定義的類信息放上去,isa
及Superclass
走向一目了然:
-
isa
走向:-
teacher
走位鏈:teacher
(對象
) --->LcrTeacher
(類
) --->LcrTeacher
(元類
) --->NSObject
(根元類
)--->NSObject
(自己
) 。 -
person
走位鏈:person
(對象
)--->LGPerson
(類
) --->LGPerson
(元類
) --->NSObject
(根元類
)--->NSObject
(自己
) 。
-
-
Superclass
走向:-
類
的走向鏈:Lcrteacher
(子類)--->LGPerson
(父類) --->NSObject
(根類) --->nil
。 -
元類
的走向鏈:LcrTeacher
(子類元類) --->LGPerson
(父類元類) --->NSObject
(根元類) --->NSObject
(根類) --->nil
。
-
objc_object & objc_class
在繼續下面類的結構分析之前,我們先來了解objc_object 和 objc_class兩個概念,以及它們之間是怎樣的關系?
objc_class:
在上篇文章iOS底層 - isa與類關聯的原理 中,通過clang編譯過的main.cpp
文件中可以看到類的底層結構:
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
NSObject
被編譯成了一個NSObject_IMPL
結構體,里面有一個Class
類型的isa
,類型Class
是由objc_class
定義的。
在源碼objc4-781
源碼中我們搜索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
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
ASSERT(isFuture() || isRealized());
data()->setFlags(set);
}
void clearInfo(uint32_t clear) {
ASSERT(isFuture() || isRealized());
data()->clearFlags(clear);
}
//略
}
從objc_class
的源碼中看出objc_class
是繼承自objc_object
的。
objc_object:
查看源碼,在objc.h及objc_private.h兩個文件中分別能找到objc_object的源碼實現:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
//略
}
那么objc_class
和objc_object
以及 NSObject
、對象
到底是什么關系?
-
objc_class
繼承自objc_object
,所以objc_class
中有一個繼承過來的isa
屬性。 - 通過
clang
編譯的main.cpp 中看出NSObject_IMPL
中有一個Class
類型的isa
,而Class
是由objc_class
定義的,而objc_class
又繼承自objc_object
,所以可知所有以NSObject
所創建的對象都有一個isa
屬性。 - 即
objc_object
是一個對象創造的根類
,所有對象創建之后都帶有objc_object
的特性,帶有一個isa
屬性。
總結
- 所有的
對象
+類
+元類
都有isa
。 - 所有的對象都是由
objc_object
繼承來的。 - 簡單概括就是
萬物皆對象
,萬物皆來源于objc_object
,有以下兩點結論:- 所有以
objc_object
為模板 創建的對象,都有isa屬性
- 所有以
objc_class
為模板,創建的類,都有isa屬性
- 所有以
- 在結構層面可以通俗的理解為
上層OC
與底層
的對接:-
下層
是通過 結構體 定義的 模板,例如objc_class
、objc_object
-
上層
是通過底層的模板創建的 一些類型,例如LGPerson
可以用下圖表示出:
objc_object & objc_class & isa
-
類的結構分析
上面分析可知類是以objc_class為模版創建的,那么我們就從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
class_rw_t *data() const {
return bits.data();
}
//省略下方方法
}
- 繼承于objc_object,默認帶有一個isa,大小為8字節。
- 父類superclass,objc_class定義的一個Class類型的結構體指針,大小也為8字節。
- cache 類的緩存信息,結構體cache_t類型,我們在下面會計算cache屬性大小。(cache是一個結構體,而不是結構體指針,不能簡單地認為大小8字節)
- bits 存放類的一些信息,class_data_bits_t 結構體類型,接下來我們就針對這個bits里存放的信息進行一步步探究。
cache大小
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; //8
explicit_atomic<mask_t> _mask; //4
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; // 8
mask_t _mask_unused; // 4
//過濾一些靜態的,因為靜態不計入結構體的大小
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags; //2
#endif
uint16_t _occupied; //2
//省略下面的方法,結構體大小只和屬性有關
}
有上面代碼可得,cache大小為16,所以8 + 8 + 16 ,想獲得bts里的信息的話,我們可利用指針偏移32來獲取類信息中的bits,這樣就可以訪問類相關的一些信息了。
其中data()方法獲取信息,是objc_class提供的方法:
class_rw_t *data() const {
return bits.data();
}
目前已經拿到了bits信息,但是還不夠,我們暫時還看不到一些具體的類的信息,所以我們繼續往下探索:
bits 查看屬性列表 property_list
由于上方獲取到的bits是class_rw_t類型結構體, 查看class_rw_t 源碼,我們可以找到類的一些相關信息:
可以看到,方法列表methods
、屬性列表properties
、協議列表protocols
等類的信息。
既然有這些信息,我們不妨試著去獲取它們,并且將它們打印:
所以,通過點方法
.properties()
,就可以獲取到類的屬性列表,再通過p *$6
打印$6
里的數據,可以獲得property_list_t
類型的屬性列表,進而打印.get()
方法,獲取每一個屬性。.get()
一直獲取屬性的話,會造成數組越界的結果,因為類的屬性個數是固定的,文章頭部的LGPerson
有兩個屬性(nickName
和name
),所以此處只能.get()
到這兩個屬性.
bits 查看屬性列表 method_list
@interface LGPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
-(void)sayHello;
+(void)say88;
@end
下面再利用lldb調試,是否也能像屬性列表一樣方式去查看類的方法列表:
由上圖可知,可以利用.methods()
獲取bits
信息中的方法列表
,獲取到的也是一個method_list_t
類型的方法列表信息,然后利用.get()
方法一個個獲取到方法。LGPerson
共有6個方法,實例方法sayHello
,.cxx_destruct
,name
屬性的set
和get
,nickName
屬性的set
和get
。
最后留下兩個問題:成員變量hobby
在哪里?以及類方法+say88
又在哪里?下篇文章我們一一揭曉。