前言:
在了解了類與isa的關聯後,了解了isa是如何關聯到類以及isa內部存在的信息分佈,接下來我們來探討類的isa走位以及類的結構底層存在了什麼呢?
類的分析:
- 延續著isa的關聯,分析isa走位,以及isa之間的繼承關係。
準備工作
- 透過以下例子,探索類的繼承關係
- 定義兩個類 Person類繼承自NSObject Teacher類繼承自Person類
//Person 類的聲明
@interface Person : NSObject
{
int _age;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHi;
+ (void)eat;
@end
//Person類的實現
@implementation Person
- (void)sayHi
{
NSLog(@"大家好");
}
+ (void)eat
{
NSLog(@"吃吃吃");
}
@end
//----------------------------------------------------------------------------
//Teacher類的聲明
@interface Teacher : NSObject
@end
//Teacher類的實現
@implementation Teacher
@end
//----------------------------------------------------------------------------
//main.m文件
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p1 = [[Person alloc]init];
Teacher *t1 = [[Teacher alloc]init];
NSLog(@"Hello, World! %@ - %@",p1,t1);
}
return 0;
}
打印調試
- 先下一個斷點在Teacher,即已執行Person *p1 = [[Person alloc]init];
問題
- 為什麼
p/x 0x001d80010000227d & 0x00007ffffffffff8ULL
打印出的信息與p/x 0x0000000100002250 & 0x00007ffffffffff8ULL
都是LGPerson
?-
0x001d80010000227d
是p1對象的isa指針地址,其&後得到的結果是創建person的类LGPerson -
0x0000000100002250
是isa中獲取的類信息所指的類的isa的指針地址,即LGPerson類的類 的isa指针地址,在Apple中,我们簡稱LGPerson類的類為元類
。
-
什麼是元類
- 我們知道
對象
的isa
是指向類
,類
的其實也是一個對象
,可以稱為類對象
,其isa
的位域指向蘋果定義的元類
-
元類
是系统
給的,其定義和創建都是由編譯器完成,在這個過程中,類對象指向元類。 -
元類
是類對象
的類
,每個類
都有一個獨一無二的元類
用來存儲類方法的相關信息
。 -
元類
本身是沒有名稱的
,由於與類
相關聯,所以使用了一樣的名稱
NSObject有幾個?
- 從圖中可以看出,最後的根元类是NSObject,這個NSObject與我們日開開發中所知道的NSObject是同一個嗎?
證明NSObject只有一份
lldb命令驗證
- 我們也通過lldb調試,來驗證這兩個NSObject是否是同一個,如下圖所示
- 從圖中可以看出,最後
NSObject
類的元類
也是NSObject
,與上面的LGPerson
中的根元類
(NSObject)的元類
,是同一個,所以可以得出一個結論:内存中只存在存在一份根元類NSObject,根元類的元類是指向它自己
程式碼驗證
- 通過三種不同的方式獲取類,看他們打印的地址是否相同
void testClassNum(){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
NSLog(@"\\n%p-\\n%p-\\n%p-\\n", class1, class2, class3);
}
- 打印結果,三個地址都相同,證明NSObject只有一份,即NSObject根元類在內存中只存一份
類存在幾份?
由於類的信息在內存中永遠只存在一份,所以類對象只有一份
isa指向&繼承關係(經典圖)
- 以上驗證了,對象,類,元類,根元類的關係圖如下。
isa指向
isa的走向有以下幾點說明:
-
實例對象(Instance of Subclass)
的isa
指向類對象(class)
-
類對象(class)
isa
指向元類(Meta class)
-
元類(Meta class)
的isa
指向根元類(Root metal class)
-
根元類(Root metal class)
的isa
指向它自己
本身,就是NSObject
superclass指向
superclass(即繼承關係)的走向也有以下幾點說明:
- 類之間的繼承關係
-
類(subClass)
繼承自父類(superClass)
-
父類(superClass)
繼承自根類(RootClass)
,此時的根類是指NSObject
-
根類
繼承自nil
,所以根類
即NSObject
可以理解為萬物起源
,即無中生有
-
-
元類
也存在繼承
,元類之間的繼承關係如下:-
子類的元類(metal SubClass)
繼承自父類的元類(metal SuperClass)
-
父類的元類(metal SuperClass)
繼承自根元類(Root metal Class)
-
根元類(Root metal Class)
繼承於根類(Root class)
,此時的根類是指NSObject
-
【注意】實例對象
之間沒有繼承關係
。
isa 指向鏈
- teacher的isa走位鏈:
t1(子類对象) --> Teacher(子類)--> Teacher(子元類) --> NSObject(根元類) --> NSObject(根根元類,即自己)
- person的isa走位圖:
p1(父類对象) --> Person (父類)--> Person(父元類) --> NSObject(根元類) --> NSObject(根根元类,即自己)
superclass繼承鏈
- 類的繼承關係鏈:
Teacher(子類) --> Person(父類) --> NSObject(根類)--> nil
- 元類的繼承關係鏈:
Teacher(子元類) --> Person(父元類) --> NSObject(根元類)--> NSObject(根類)--> nil
為什麼對象和類都有isa屬性?
- 了解了isa指向,以及類的繼承,但是為什麼對象與類都有isa屬性呢,請接下看下去。
探索objc_class & objc_object
- 我們透過isa與類的關係這篇文章中,利用了clang編譯main.m文件,從從編譯後C++文件中,可以看到如下C++源碼。
-
NSObject
的底層編譯是NSObject_IMPL
結構體- 其中
Class
是isa
指針類型,是由objc_class 定義類型 -
objc_class
是一個結構體,iOS中,所有Class都是以objc_class
為模板創建的
struct NSObject_IMPL { Class isa; }; typedef struct objc_class *Class;
- 其中
objc_class
在objc源碼中搜索
objc_class
的定義,可以看到舊版及新版舊版-
runtime.h
文件中struct objc_class
(棄用)
新版-
objc-runtime-new.h
中struct objc_class
(objc4-781最新優化)新版
struct objc_class
繼承自objc_object
objc_object
在objc源碼中搜索objc_object
的定義,可以看到舊版及新版
- 在源碼中可以看到
objc.h
文件中的objc_object
-
objc-privat.h
編譯後的
main.cpp
中的objc_object
的定義
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
objc_class & objc_object之間的關係
- 結構體類型
objc_class
繼承自objc_object
,objc_object
這個結構體內部有一個isa屬性,所以objc_class
也擁有isa
屬性 - main.cpp底層編譯文件中,
NSObject
中的isa
在底層是由Class
定義,其中class的底層編碼來自objc_class
類型,所以NSObject
也擁有了isa
屬性
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
NSObject
是一個類,任何類都繼承於他,使用NSObject
來實例化一個對象
,而這個對象
擁有了objc_object
的特性,等同於擁有isa
這個屬性,而這個isa
由objc_class
繼承過來,又objc_class
繼承自objc_object
,objc_object
有isa
屬性,所以對象
都有一個isa,來自於當前的objc_object。-
實例化對象底層編譯為
objc_object
,即擁有isa屬性,另外所有的類繼承於NSObject
類,經由編譯後,變成了objc_class
結構體,繼承於objc_object
自然也擁有isa屬性。objc_object 與對象的關係
- 所有的
對象
經由編譯會變成結構體objc_object
- 所有的對象是來自
NSObject
(OC),但是真正到底層的是一個objc_object(C/C++)
的結構體類型
objc_object
與對象
的關係
是繼承
關係 - 所有的
總結
- 所有的
對象
+類
+元類
都有isa
屬性 - 所有的
對象
都是由objc_object
繼承來的 - 萬物皆來源於
objc_object
,有以下兩點結論:- 所有以
objc_object
為模板創建的對象
,都有isa
屬性 - 所有以
objc_class
為模板,創建的類
,都有isa
屬性
- 所有以
- 在結構層面可以理解為上層
OC
與底層C/C++
的對接
:-
下層
是通過結構體
定義的模板
,例如objc_class、objc_object
-
上層
是通過繼承NSObject類,而NSObject底層編譯後,繼承得是擁有isa屬性的objc_object
-
類的結構分析
- 我們知道了萬物的起源
NSObject
,其實是來自於底層編譯後的objc_class
,那麼這個結構體objc_class
又有了哪些屬性呢,接著就來分析這個結構體
struct objc_class : objc_object {
// Class ISA; //8字節
Class superclass; //Class 类型 8字節
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
- isa屬性:繼承自
objc_object
的isa
,佔8字節。 - superclass屬性:
Class
類型,Class
是由objc_object
定義的,是一個指针
,佔8字節 - cache屬性:簡單從類型
cache_t
目前無法得知,而cache_t
是一個結構體類型,結構體的内存大小需要根據内部的屬性來確定,而結構體指針才是8字節。 - bits屬性:只有首地址經過上面3個屬性的內存大小總和的平移,才能獲取到bits。
計算cache_t
類的內存大小
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
#if __LP64__
uint16_t _flags; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
【情況一】if
流程
buckets
類型是struct bucket_t *
,是結構體指针
類型,佔8
字節mask
是mask_t
類型,而mask_t
是unsigned int
的別名,佔4
字節
【情況二】elseif
流程_maskAndBuckets
是uintptr_t
類型,它是一個指针
,佔8
字節_mask_unused
是mask_t
類型,而mask_t
是uint32_t
類型定義的別名,佔4
字節
計算前兩個屬性的内存大小,有以下兩種情況,內存大小總和都是
12
字節
-
_flags
是uint16_t
類型,uint16_t是unsigned short
的別名,佔2
個字節 -
_occupied
是uint16_t
類型,uint16_t是unsigned short
的別名,佔2
個字節
總結:所以最後計算出cache類的內存大小= 12 + 2 + 2 = 16字節
獲取bits
所以有上述計算可知,想要獲取bit的內容,只需要通過類的首地址平移32個字節即可。
以下通過lldb命令調適的過程。
-
其中的data()就是objc_class的提供的方法,透過訪問data()獲取bits數據
- 從對象
$2
可以看到data()
的數據,是一個結構體,但內部沒有屬性列表
,方法列表
,那我們查看他的類型(class_rw_t)
探索屬性列表(property list)
- 透過查看類型
class_rw_t
,可以發現這個結構體class_rw_t
有提供methods(),properties(),protocols()
方法獲取屬性列表,方法列表。
- 了解結構體
class_rw_t
內部提供的方法,我們繼續探索屬性列表,流程如下。
-
p $3.properties()
命令中的properties方法是由class_rw_t提供的,方法中返回的實際類型為property_array_t
。 - 由於
list
的類型是property_list_t
,是一個指針,所以通過p *$4
獲取內存中的信息,同時也證明bits
中存儲了property_list
,即屬性列表。 - 但是當我們想取得成員變量,卻發現
數組越界
,那麼也代表property_list
中只有一個屬性name。
獲取成員變量存儲位置
由此可得出
property_list
中只有屬性,沒有成員變量。屬性與成員變量兩者差異,就是屬性
@property
編譯後有生成get
跟set
方法接下來我們通過查看
objc_class
中bits
屬性中存儲數據的類class_rw_t
的定義發現,除了methods、properties、protocols
方法,還有一個ro
方法,其返回類型是class_ro_t
,通過查看其定義,發現其中有一個ivars
屬性,我們可以做如下猜測:是否成員變量就存儲在這個ivar_list_t
類型的ivars
屬性中呢?探索成員變量(lldb調適流程)
class_ro_t結構體中的屬性如下所示,想要獲取ivars,需要ro的首地址平移48字節
struct class_ro_t {
uint32_t flags; //4
uint32_t instanceStart;//4
uint32_t instanceSize;//4
#ifdef __LP64__
uint32_t reserved; //4
#endif
const uint8_t * ivarLayout; //8
const char * name; //8
method_list_t * baseMethodList; // 8
protocol_list_t * baseProtocols; // 8
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
}
總結
- 通過@interface{ }定義的成員變量,會儲存在類的bits屬性中,通過
bits→data()→ro()→ivars
獲取成員變量列表,有成員變量(hobby)以及屬性定義的成員變量(name) - 通過@property定義的屬性,也會存儲在bits屬性中,通過
bits → data() → properties() → list
獲取属性列表,其中只包含屬性
探索方法列表,即methods_list
- 在LGPerson類裡面添加兩個方法
@interface LGPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)sayHappy;
@implementation LGPerson
- (void)sayHello
{
}
+ (void)sayHappy
{
}
@end
-
通過lldb調適來獲取方法列表,步驟如圖所示
通過
p $3.methods()
獲得具體的方法列表
的list
結構,其中methods
也是class_rw_t
提供的方法通過打印的
count = 4
可知,存儲了4
個方法,可以通過p $6.get(i)
內存偏移
的方式獲取單個方法,i的範圍是0-3
如果在打印
p $6.get(4)
,獲取第五個方法,也會報錯,提示數組越界
-
如此以來可以找到對象方法,但是類方法呢?接著我們探索類方法的儲存位置
類方法儲存位置
- 透過上述查找methods→list 內部只有實例方法,沒有發現類方法。
- 類對象的isa指向元類,元類用來儲存類的相關信息,於是猜測類方法是否儲存在元類的bits中,如下圖透過lldb調適流程。
- 通過元類方法列表打印結果,確實類方法儲存在元類裡
總結
- 類的
實例方法
儲存在類的bits屬性中,通過bits --> methods() --> list
獲取方法實例方法實例列表,列表中除了擁有實例方法,還有set方法即get方法。 - 類的
類方法
儲存在元類的bits屬性中,通過元类bits --> methods() --> list
獲取類方法列表。