本文旨在經過類&元類的建立時機
,類的結構及相關屬性
,添加的類信息
等分析類在內存中的實際存在,並分享一些關於類的經典面試題
上文說到,對象經過isa
和類關聯,同個類型的對象能夠屢次建立,因此對象能夠有多個。那麼類呢,根據開發經驗,很容易得出類在內存中只有一個,那究竟要怎麼實錘呢。提供驗證方式:html
machoView
查看新建一個項目,並建立一個CJPerson
類,在Products
目錄下.app
結尾的文件尚未編譯是報紅的,c++
command + b
下變黑,Show In Finder
後顯示包內容,把可執行文件拖入machoView
,
面試
machoView
中顯示以下
緩存
能夠看到,在DATA
段的_objc_classrefs
內已經加載了CJPerson
類,並指定了內存地址,說明類的建立是在編譯時期,而且只有一份。bash
元類也是在編譯期,由系統建立的,在個人理解,元類的做用是:
1.承接isa走向
2.緩存類方法
總以爲蘋果大費周章引入元類的概念,還有其餘的做用,至因而什麼,但願能夠指導我下
複製代碼
在main.m
下初始化一個CJPerson
對象數據結構
int main(int argc, const char * argv[]) {
@autoreleasepool {
CJPerson * person = [[CJPerson alloc]init];
NSLog(@"%@",person.class);
}
return 0;
}複製代碼
用clang
編譯下main.m
多線程
clang -rewrite-objc main.m -o main.cpp複製代碼
打開編譯後的main.cpp
文件,直接來到最後面,會看到初始化時候是調用objc_getClass
app
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
CJPerson * person = ((CJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((CJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CJPerson"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_3995175d2_v6g81lknk8jdwh0000gn_T_main_e6fa2f_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("class")));
}
return 0;
}複製代碼
在文件內搜索objc_getClass
,看到ide
__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);複製代碼
再搜索objc_class
,找到重定義post
typedef struct objc_class *Class;複製代碼
原來類是objc_class
重定義的,感受這個objc_class
很熟悉,好像在objc源碼裏面有看到過,那就搜索試試
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();
}
...
}複製代碼
順便貼出objc_object的結構
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};複製代碼
ISA
是被註釋的,不表明結構體內不包含它,由於objc_class
繼承objc_object
,因此ISA
是來自於父類clang
編譯下NSObject
能夠獲得objc_object
isa
的結構是isa_t
,但是這裏使用class
接收是能夠的,內部經過shift_class
獲得class
總結:類的真正類型爲objc_class,繼承於objc_object(也就是NSObject的底層結構),說明萬物皆對象,類也是一個對象,CJPerson嚴格來講是類對象
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
}複製代碼
根據objc_class
結構體能夠清楚看到,類有4個屬性,分別是isa
,superclass
,cache
,bits
isa
在(iOS底層-一應俱全的isa)中已經分析過了,佔有8個字節,只是這裏的isa是指向元類
superclass
就是指向的父類
typedef struct objc_class *Class;
複製代碼
superclass
的結構一樣是objc_class
,由於它是結構體指針
,佔有8個字節
cache
是方法緩存,方法緩存涉及到方法查找流程
,緩存策略
,動態擴容
等,下一章詳細說明,先看下cache_t
和bucket_t
結構
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}複製代碼
struct bucket_t {
private:
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
...
}複製代碼
buckets
是裝方法的桶子,裏面放着方式實現imp
,根據方法編號sel
生成的key
mask
是擴容因子,決定擴容的時機
occupied
是當前佔用的容量
bucket_t
是結構體指針,佔用8個字節,mask_t
就是int
,佔用4個字節,因此mask
和occupied
各佔4個字節
cache_t
是個結構體,結構體大小是內部全部大小的和,因此cache_t
佔有16個字節
bits
是數據存放的地方,bits
裏面有data
,data
是從macho
裏面讀取的class_rw_t
class_rw_t *data() {
return bits.data();
}複製代碼
struct class_rw_t {
...
const class_ro_t *ro;
method_array_t methods;//方法列表
property_array_t properties;//屬性列表
protocol_array_t protocols;//協議列表
...
}複製代碼
class_rw_t
有方法列表,屬性列表,協議列表等等
class_rw_t
裏面有個class_ro_t
struct class_ro_t {
..
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;
}
};複製代碼
class_ro_t
也有方法列表,屬性列表,協議列表等,可是多了ivar_list_t實例變量列表。下面探索下二者等區別
類中添加實例變量sex
,屬性age
,對象方法work
,類方法play
@interface CJPerson : NSObject{
NSString * sex;
}
@property (nonatomic , copy , readonly) NSString * age;
- (void)work;
+ (void)play;複製代碼
object_getClass
輸出下類對象
CJPerson * person = [CJPerson alloc];
Class cls = object_getClass(person);
NSLog(@"%@",cls);複製代碼
x/4gx
打印cls
的內存地址以下:
經過類的數據結構分析,bits
位於ISA,superclass,cache
以後,這三者分別是8,8,16字節,根據內存偏移,
bits
在isa
地址後的32字節,
0x100001218 + 32字節 = 0x100001238複製代碼
po
和p
直接打印都不行,那麼須要用class_data_bits_t
類型強轉打印
取出bits
內的class_rw_t
看到methods
中有3個方法,分別輸出下
.cxx_destruct 系統添加c++的析構方法
work 對象方法
age 屬性生成的age方法
method_t中的types,在蘋果開發者文檔中有詳細說明
結論:有對象方法work
,竟然沒有類方法play
如法炮製,輸出下屬性列表properties
結論:符合預期,只有一個添加的age
屬性
那繼續看下class_ro_t
結論:ivar_list_t
中有添加的實例變量sex
,還有一個_age
,這也符合常規認知,屬性在底層會生成帶下劃線的實例變量
結論:和class_rw_t
中的methods
一致
結論:和class_rw_t
中的properties
一致
目前添加的實例變量,屬性,對象方法都在內存中尋找到了,惟獨缺乏類方法,不過根據經驗能夠知道,類方法是緩存在元類
中,那嘗試去元類
搜索
經過isa_mask
找到元類,而後一步步找到baseMethodList
,果真在其中找到類方法play
class_ro_t
中的ivar_list_t
class_rw_t
中的property_array_t
和class_ro_t
中的的property_list_t
都存着一份,而且會生成實例變量,和對應的方法class_rw_t
中的method_array_t
和class_ro_t
中的的method_list_t
都存着一份類
裏面元類
裏面class_ro_t和class_rw_t內容大部分相同的緣由:
class_ro_t
存儲了當前類在編譯期就已經肯定的屬性
、方法
以及遵循的協議,
class_rw_t
是在運行時才肯定,它會先將class_ro_t
的內容拷貝過去,而後再將當前類的分類的這些屬性、方法等拷貝到其中。class_rw_t
是class_ro_t
的超集。
這是一道比較經典的面試題,CJPerson
繼承NSObject
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[CJPerson class] isKindOfClass:[CJPerson class]]; //
BOOL re4 = [(id)[CJPerson class] isMemberOfClass:[CJPerson class]]; //
NSLog(@"%hhd%hhd%hhd%hhd",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[CJPerson alloc] isKindOfClass:[CJPerson class]]; //
BOOL re8 = [(id)[CJPerson alloc] isMemberOfClass:[CJPerson class]]; //
NSLog(@"%hhd%hhd%hhd%hhd",re5,re6,re7,re8);複製代碼
輸出爲1000,1111
re1是NSObject調用類方法isKindOfClass
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}複製代碼
遞歸查找NSObject的元類是否存在,存在就繼續找元類的父類,直到根元類指向NSObject的時候,返回YES複製代碼
re2是NSObject調用類方法isMemberOfClass
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}複製代碼
只對比一次,NSObject的元類是否等於NSObject,返回NO複製代碼
re3是CJPerson調用類方法isKindOfClass
第一步就到CJPerson元類,而後一直遞歸下去,不會回到CJPerson類,返回NO複製代碼
re4是CJPerson調用類方法isMemberOfClass
只對比一次CJPerson元類和CJPerson類,返回NO複製代碼
re5是NSObject對象調用對象方法isKindOfClass
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}複製代碼
遞歸查找對象的父類是否等於NSObject,總有一次等於NSObject,返回YES複製代碼
re6是NSObject對象調用對象方法isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}複製代碼
只對比一次,NSObject對象的類是否等於NSObject,恰好繼承關係知足複製代碼
re7是CJPerson對象調用對象方法isKindOfClass
遞歸查找CJPerson對象的類是否等於CJPerson,第一次就知足繼承關係複製代碼
re8是CJPerson對象調用對象方法isMemberOfClass
只對比一次,CJPerson對象的類是否等於CJPerson,恰好繼承關係知足複製代碼
以上就是關於類的探索,下一章是cache_t
流程分析引出消息發送流程,後續繼續更新類的底層結構,block,鎖,多線程等底層探索,還有應用程序加載,啓動優化,內存優化等相關知識點,敬請關注。