開發中常常建立一個 TestClass.h
和 TestClass.m
文件,而這個 TestClass
就是咱們所謂的類,那類的結構究竟是怎樣的呢?數組
新建一個類 TestClass
:bash
@interface TestClass : NSObject {
NSString *flyIvar;
}
@property (nonatomic, strong) NSString *flyProperty;
@end
@implementation TestClass
@end
複製代碼
在 main.m
的 main
函數中寫入下方代碼:函數
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
TestClass *object = [[TestClass alloc] init];
Class pClass = object_getClass(object);
NSLog(@"%p - %p",object,pClass);
}
return 0;
}
複製代碼
而後經過命令行工具將 main.m
轉換成 C++
編譯的文件 main.cpp
,命令以下: clang -rewrite-objc main.m -o main.cpp
工具
打開 main.cpp
到最底部,就能看到 main
函數中的東西在 C++
編譯成了以下代碼:學習
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
TestClass *object = ((TestClass *(*)(id, SEL))(void *)objc_msgSend)((id)((TestClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestClass"), sel_registerName("alloc")), sel_registerName("init"));
Class pClass = object_getClass(object);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cj_20kzp4xj3x32g8_b79x0tg4h0000gn_T_main_508fc7_mi_0,object,pClass);
}
return 0;
}
複製代碼
查找到 Class
的定義爲:typedef struct objc_class *Class;
,查看一下 objc_class
的結構體定義:ui
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();
}
//...
}
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
複製代碼
上方代碼就能看到 objc_class
繼承於 objc_object
,從這裏看到 OC
萬物之祖爲結構體類型,也就是編譯器在編譯的時候會把咱們的類編譯成結構體類型,而且在結構體中第一個爲 isa
,第二個爲當前類的父類,第三個爲 cache_t cache
,第四個爲class_data_bits_t bits
。atom
那麼問題來了,上方 TestClass
中的屬性 @property (nonatomic, strong) NSString *flyProperty;
和成員變量 NSString *flyIvar;
在哪兒存放着呢?spa
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;
//...
}
複製代碼
通過一番查找在 bits.data();
返回的 class_rw_t
裏的 class_ro_t *ro
中發現了咱們須要的屬性列表 property_list_t *baseProperties;
,那怎麼取到 class_data_bits_t bits
呢?命令行
接下來看一下 TestClass
的內存結構: 3d
從上方的內存結構中,能看到第二個匹配上了 superclass
,可是第三和第四個殊不知道是什麼。
對這個內存結構進行分析一下:
指向當前類的指針首地址爲 0x100001200
,咱們知道第一個 0x001d8001000011d9
是 isa
佔用 8 字節,因此咱們用 0x100001200 + 8
打印一下, 輸出了 <NSObject: 0x100001208>
,而 superclass
是 Class
類型的,Class
類型是一個 struct objc_class *
的結構體指針,因此它也佔用 8 字節。
接下來就到 0x100001210
了,嘗試將 0x100001210
強轉成 (cache_t *)
類型輸出一下,而後調用 cache_t
結構體裏面的 mask()
方法打印一下,發現輸出了 (mask_t)
類型的 $6
,就肯定了 0x100001210
指針地址指向的爲 cache_t cache
。
肯定了 cache_t cache
的指針首地址,還須要知道 cache_t cache
大小才能知道 class_data_bits_t bits
的首地址,此時就須要對 cache
的大小進行計算了。
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
//...
}
複製代碼
cache
是一個 cache_t
的 struct
結構體類型,結構體類型的大小計算須要計算結構體裏全部成員的大小。計算以下:
_buckets
爲 struct bucket_t *
的結構體指針類型佔用 8 字節,_mask
和 _occupied
都是 uint32_t
類型,uint32_t
佔用 4 字節,方法不佔內存空間,則 cache
佔用內存爲 16 字節。
計算了 cache
的佔用內存,那麼 class_data_bits_t bits
的首地址就爲 0x100001210 + 16
= 0x100001220
。
接下來就要獲取 class_data_bits_t
中存儲的東西了, 強轉 0x100001220
類型爲 (class_data_bits_t *)
,獲取 bits -> data()
返回的 class_rw_t *
並打印顯示以下結果。
獲取 class_rw_t
中的 class_ro_t *ro
並打印,baseProperties
中存儲的就是 屬性變量,ivars
中存儲的是 成員變量。
分別打印 baseProperties
和 ivars
:
baseProperties
是一個數組,數組中存儲了一個元素,就是 @property (nonatomic, strong) NSString *flyProperty;
ivars
一樣是一個數組,可是這個數組中存儲了兩個元素,第一個爲 NSString *flyIvar;
,第二個爲 NSString *_flyProperty;
,由於屬性會自動生成一個帶下劃線的成員變量。這樣就找到屬性變量和成員變量存儲的地方。那麼方法列表呢?
同理以獲取 baseProperties
的方式獲取 baseMethodList
,第一個是 flyProperty
的 getter
方法,第二個是 flyProperty
的 setter
方法,第三個是編譯成 C++
代碼系統添加的。
上方的打印是隻有一個屬性變量,沒有任何方法添加,如今添加一個類方法和一個實例方法再次打印看看。
//TestClass.h
@interface TestClass : NSObject {
NSString *flyIvar;
}
@property (nonatomic, strong) NSString *flyProperty;
- (void)testClassInstanceMethod;
+ (void)testClassClassMethod;
@end
//TestClass.m
@implementation TestClass
- (void)testClassInstanceMethod {
NSLog(@"testClassInstanceMethod");
}
+ (void)testClassClassMethod {
NSLog(@"testClassClassMethod");
}
複製代碼
在這裏發現少了一個方法 + (void)testClassClassMethod;
,那這個方法存儲在哪兒了?
咱們知道類裏面存儲的是實例對象的方法,元類裏面存儲的是類對象的方法,既然瞭解了這個就使用 isa
從元類裏面查找吧。
在推導的過程能夠看到對象的實例方法在類對象存儲,相對於元類,類方法又屬於元類的實例方法。
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製代碼
NSObject (Class)
和 objc_class
的關係:NSObject (Class)
是 objc_class
類型呢,在編譯的時候會編譯成 objc_class
。
NSObject
和 objc_object
的關係:NSObject
是對 objc_object
結構體的仿寫,只是一個是咱們 OC
對象的,一個是編譯器編譯事後底層真正運行的。
這裏有一個小細節 ,爲何 struct objc_object
中 isa
的是 Class
類型的呢?咱們學習的過程明明知道 isa
是一個 isa_t
類型的聯合體,那這裏的 isa
怎麼不是 isa_t
類型的?
這是由於在早期的 iOS
調用 isa
就是爲了返回 Class
的,當咱們調用了 object_getClass()
時,OC
底層源碼調用的是 obj->getIsa();
,接着進入 getIsa()
,又 return ISA()
,在 ISA()
中就能看到早期代碼使用 (Class)isa.bits;
返回,如今使用 (Class)(isa.bits & ISA_MASK);
返回,也就是說明了在返回時將二進制強轉成了一個 Class
類型的結構體,這也是 isa
是 Class
類型的緣由。
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
//這裏是返回類對象須要用 isa 的指針 & ISA_MASK
return (Class)(isa.bits & ISA_MASK);
#endif
}
複製代碼
以上就是對類結構裏面屬性變量、成員變量的獲取和實例方法、類方法獲取的分析。