底層原理 類的本質
複習一下IOS 底層原理 物件的本質--(1),可以看出來例項物件實際是上結構體,那麼這個結構體是有類指標和成員變數組成的。
//Person
@interface Person : NSObject
{
@public
int _age;//4bytes
int _level;//4bytes
int _code;//4bytes
}
@end
@implementation Person
@end
複製程式碼
經過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main64.cpp
編譯之後其實Person
物件是:
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _level;
int _code;
};
複製程式碼
NSObject_IMPL
結構體:
struct NSObject_IMPL {
Class isa;
};
複製程式碼
那麼NSObject
在記憶體中包括
isa
指標- 其他成員變數
isa
地址就是instance
的地址,其他成員變數排在後邊,也就是instance
的地址就是isa
的地址。
那麼這個isa
指向的到底是什麼呢?
請往下繼續看:
先看下這段程式碼:
NSObject *ob1=[[NSObject alloc]init];
NSObject *ob2=[[NSObject alloc]init];
Class cl1 = object_getClass([ob1 class]);
Class cl2 = object_getClass([ob2 class]);
Class cl3 = ob1.class;
Class cl4 = ob2.class;
Class cl5 = NSObject.class;
NSLog(@" %p %p %p %p %p",cl1,cl2,cl3,cl4,cl5);
//0x7fff8e3ba0f0 0x7fff8e3ba0f0
//0x7fff8e3ba140 0x7fff8e3ba140 0x7fff8e3ba140
複製程式碼
這程式碼是輸出了幾個NSObject
的物件的類和NSObject
的類物件的地址,可以看到cl1==cl2
、cl3==cl4==cl5
。
Class的本質
我們知道不管是類物件還是元類物件,型別都是Class,class和mete-class的底層都是objc_class結構體的指標,記憶體中就是結構體。
Class objectClass = [NSObject class];
Class objectMetaClass = object_getClass([NSObject class]);
複製程式碼
點選class來到內部,可以發現
typedef struct objc_class *Class;
複製程式碼
class
物件其實是指向objc_class的結構體,因此我們可以說類物件或元類物件在記憶體中其實就是objc_class結構體。
來到objc_class
內部,在原始碼中經常看到這段原始碼
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;//isa
#if !__OBJC2__
Class _Nullable super_class //父類 OBJC2_UNAVAILABLE;
const char * _Nonnull name //obj名字 OBJC2_UNAVAILABLE;
long version //版本 OBJC2_UNAVAILABLE;
long info //info OBJC2_UNAVAILABLE;
long instance_size // OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars //成員變數連結串列 OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;//方法連結串列
struct objc_cache * _Nonnull cache //快取連結串列 OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols //協議連結串列 OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
複製程式碼
這段程式碼明顯是 已經OBJC2_UNAVAILABLE
,說明程式碼已經不在使用了。那麼objc_class
結構體內部結構到底是什麼呢?通過objc搜尋runtime
的內容可以看到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() {
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_object
並且結構體內有一些函式,因為這是c++
結構體,在c上做了擴充套件,因此結構體中可以包含函式。我們來到objc_object
內,擷取部分程式碼
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
複製程式碼
那麼我們之前瞭解到的,類中儲存的類的成員變數資訊,方法列表,協議列表,擷取class_rw_t
內部實現程式碼
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;//方法列表
property_array_t properties;//屬性列表
protocol_array_t protocols;//協議列表
Class firstSubclass;
Class nextSiblingClass;
**
//後邊省略
}
複製程式碼
而class_rw_t
是通過bits.data()
獲取的,擷取bits.data()
檢視內部實現,而僅僅是bits&FAST_DATA_MASK
。
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
複製程式碼
而成員變數則是儲存在class_ro_t
內部中的,我們來到class_ro_t
內部檢視:
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;//屬性列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};
複製程式碼
最後通過一張圖總結一下:
那麼我們來證明一下: 我們可以自定義一下一個和系統一樣的結構體,那麼我們當我們強制轉化的時候,他們賦值會一一對應,此時我們就可以拿到結構體的內部的值。 下邊程式碼是我們自定義的值://
// Header.h
// day02-類的本質1
//
// Created by Charlie on 2019/7/2.
// Copyright © 2019 www.fgyong.cn. All rights reserved.
//
#ifndef Header_h
#define Header_h
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
struct cache_t {
bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
};
struct method_t {
SEL name;
const char *types;
IMP imp;
};
struct method_list_t : entsize_list_tt {
method_t first;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t alignment_raw;
uint32_t size;
};
struct ivar_list_t : entsize_list_tt {
ivar_t first;
};
struct property_t {
const char *name;
const char *attributes;
};
struct property_list_t : entsize_list_tt {
property_t first;
};
struct chained_property_list {
chained_property_list *next;
uint32_t count;
property_t list[0];
};
typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
uintptr_t count;
protocol_ref_t list[0];
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance物件佔用的記憶體空間
#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;
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t * methods; // 方法列表
property_list_t *properties; // 屬性列表
const protocol_list_t * protocols; // 協議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t* data() { // 提供data()方法進行 & FAST_DATA_MASK 操作
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
/* OC物件 */
struct xx_objc_object {
void *isa;
};
/* 類物件 */
struct fy_objc_class : xx_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
fy_objc_class* metaClass() { // 提供metaClass函式,獲取元類物件
// 上一篇我們講解過,isa指標需要經過一次 & ISA_MASK操作之後才得到真正的地址
return (fy_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* Header_h */
複製程式碼
這段程式碼親測可用,直接複製自己新建.h
檔案匯入'main.m'即可,將main.m
改成main.mm
或者將其他某一個.m
改成.mm
執行就可以執行了。
那麼我們再拿出來經典的那張圖挨著分析isa
和superclass
的指向
instance 物件驗證
使用 p/x
輸出obj
16進位制的地址,然後isa指標需要經過一次 & ISA_MASK操作之後才得到真正的地址。實施之後:
//object
Printing description of student:
<Student: 0x1021729c0>
(lldb) p/x object->isa //檢視isa指標地址
(Class) $0 = 0x001dffff8e3ba141 NSObject
(lldb) p/x objectClass//輸出 objectClass的地址
(fy_objc_class *) $1 = 0x00007fff8e3ba140
(lldb) p/x 0x001dffff8e3ba141&0x00007ffffffffff8//計算得出object->isa真正的地址
(long) $2 = 0x00007fff8e3ba140 //0x00007fff8e3ba140是 objectClass地址和object->isa地址一樣
//person
Printing description of person: <Person: 0x102175300>
(lldb) p/x person->isa
(Class) $3 = 0x001d800100002469 Person
(lldb) p/x 0x001d800100002469&0x00007ffffffffff8
(long) $4 = 0x0000000100002468
(lldb) p/x personClass
(fy_objc_class *) $5 = 0x0000000100002468//isa 和personclass地址都是0x0000000100002468
//student
(lldb) p/x student->isa
(Class) $6 = 0x001d8001000024b9 Student
(lldb) p/x 0x001d8001000024b9&0x00007ffffffffff8
(long) $7 = 0x00000001000024b8
(lldb) p/x studentClass
(fy_objc_class *) $8 = 0x00000001000024b8//studentclass 和isa地址都是0x00000001000024b8
(lldb)
複製程式碼
從面的輸出結果中我們可以發現instance物件中確實儲存了isa指標和其成員變數,同時將instance物件的isa指標經過&運算之後計算出的地址確實是其相應類物件的記憶體地址。由此我們證明isa,superclass指向圖中的1,2,3號線。
class 物件驗證
接著我們來看class
物件,同樣通過上一篇文章,我們明確class
物件中儲存著isa
指標,superclass
指標,以及類的屬性資訊,類的成員變數資訊,類的物件方法,和類的協議資訊,而通過上面對object
原始碼的分析,我們知道這些資訊儲存在class
物件的class_rw_t
中,我們通過強制轉化來窺探其中的內容
//objectClass and objectMetaClass
(lldb) p/x objectClass->isa
(__NSAtom *) $6 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1&0x00007ffffffffff8
(long) $7 = 0x00007fff8e3ba0f0
(lldb) p/x objectMetaClass
(fy_objc_class *) $8 = 0x00007fff8e3ba0f0
//personClass and personMetaClass
(lldb) p/x personClass->isa
(__NSAtom *) $9 = 0x001d800100002441
(lldb) p/x personMetaClass
(fy_objc_class *) $10 = 0x0000000100002440
(lldb) p/x 0x001d800100002441&0x00007ffffffffff8
(long) $11 = 0x0000000100002440
//sutdentClass and studentMetaClass
(lldb) p/x studentClass->isa
(__NSAtom *) $12 = 0x001d800100002491
(lldb) p/x 0x001d800100002491&0x00007ffffffffff8
(long) $13 = 0x0000000100002490
(lldb) p/x studentMetaClass
(fy_objc_class *) $14 = 0x0000000100002490
複製程式碼
有此結果得知objectMetaClass==objectClass->isa==0x00007fff8e3ba0f0
,personClass->isa==personMetaClass==0x0000000100002440
,studentClass->isa==studentMetaClass==0x0000000100002490
。
由此我們證明isa,superclass指向圖中,isa指標的4,5,6號線,以及superclass指標的7,8,9號線。
meta-class物件驗證
最後我們來看meta-class
元類物件,上文提到meta-class
中儲存著isa
指標,superclass
指標,以及類的類方法資訊。同時我們知道meta-class
元類物件與class
類物件,具有相同的結構,只不過儲存的資訊不同,並且元類物件的isa
指標指向基類的元類物件,基類的元類物件的isa
指標指向自己。元類物件的superclass
指標指向其父類的元類物件,基類的元類物件的superclass
指標指向其類物件。
與class
物件相同,我們同樣通過模擬對person
元類物件呼叫.data
函式,即對bits
進行&FAST_DATA_MASK(0x00007ffffffffff8UL)
運算,並轉化為class_rw_t
。
// objectMetaClass->superclass = 0x00007fff8e3ba140 NSObject
//objectMetaClass->isa = 0x00007fff8e3ba0f0
//objectMetaClass = 0x00007fff8e3ba0f0
(lldb) p/x objectMetaClass->superclass
(Class) $20 = 0x00007fff8e3ba140 NSObject
(lldb) p/x objectMetaClass->isa
(__NSAtom *) $21 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1&0x00007ffffffffff8
(long) $22 = 0x00007fff8e3ba0f0
(lldb) p/x objectMetaClass
(fy_objc_class *) $23 = 0x00007fff8e3ba0f0
// personMetaClass->superclas=0x00007fff8e3ba0f0
//personMetaClass->isa=0x00007fff8e3ba0f0
//personMetaClass = 0x0000000100002440
(lldb) p/x personMetaClass->superclass
(Class) $25 = 0x00007fff8e3ba0f0
(lldb) p/x personMetaClass->isa
(__NSAtom *) $26 = 0x001dffff8e3ba0f1
(lldb) p/x personMetaClass
(fy_objc_class *) $30 = 0x0000000100002440
// studentMetaClass->superclas=0x0000000100002440
//studentMetaClass->isa=0x00007fff8e3ba0f0
(lldb) p/x studentMetaClass->superclass
(Class) $27 = 0x0000000100002440
(lldb) p/x studentMetaClass->isa
(__NSAtom *) $28 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1 & 0x00007ffffffffff8
(long) $29 = 0x00007fff8e3ba0f0
複製程式碼
有上面可以看出,studentMetaClass->isa
,personMetaClass->isa
,objectMetaClass->isa
結果mask
之後都是0x00007fff8e3ba0f0
,與p/x objectMetaClass
結果一致,則驗證了13,14,15號線,studentMetaClass->superclass =0x0000000100002440
,personMetaClass = 0x0000000100002440
驗證12號線,personMetaClass->isa=0x00007fff8e3ba0f0
和objectMetaClass = 0x00007fff8e3ba0f0
驗證了11號線,objectMetaClass->superclass = 0x00007fff8e3ba140 NSObject
驗證10號線。
總結:
物件的isa指向哪裡?
- instance物件的isa指向class物件
- class物件的isa指向meta-class物件
- meta-class物件的isa指向基類的meta-class物件
- class和meta-class的記憶體結構一樣的,只是值不一樣
OC的類資訊存放在哪裡?
- 物件方法、屬性、成員變數、協議資訊存放在class物件中
- 類方法存放在meta-class物件中
- 成員變數具體值存放在instance物件中
資料下載
最怕一生碌碌無為,還安慰自己平凡可貴。
本文章之所以圖片比較少,我覺得還是跟著程式碼敲一遍,印象比較深刻。