前言:
在了解了類與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獲取類方法列表。



