本文主要寫一下,runtime中關於類,元類的結構和他們之間的關係。其實應該在上一篇文章面試遇到Runtime的第一天中先寫本文的內容,可是寫那天恰好在整理category的知識點,因此趁熱打鐵的就寫在了上一篇文章。若是在閱讀時遇到有比較難理解的點,不妨能夠先閱讀本文,再去閱讀面試遇到Runtime的第一天中的內容。面試
閱讀過面試遇到Runtime的第一天,你確定就已經知道,runtime通俗點說就是一套底層API,那麼咱們經過什麼方式能夠調用到它呢?api
咱們編寫的OC代碼,在編譯階段會自動轉換成運行時代碼緩存
NSObject能夠看作是全部類的基類(NSProxy除外),NSObject協議中定義了以下這些能夠從runtime中獲取信息的方法bash
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
複製代碼
固然咱們也能夠手動調用runtime API,須要導入objc/Runtime.h和objc/message.h兩個頭文件less
先看NSObject的定義post
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製代碼
只有一個Class類型的isa,找到Class的定義,是一個objc_class的結構體ui
typedef struct objc_class *Class;
複製代碼
這裏咱們直接看Objc2.0以後,objc_class的定義(忽略了部分本文不討論的代碼)this
typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface Object {
Class isa;
}
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
isa_t isa;
}
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
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
複製代碼
從源碼中咱們能夠證明如下幾點:spa
這張圖中的關係咱們須要好好的理解(而且記憶)一下,也有助於咱們以後對runtime的理解指針
元類這個概念比較抽象,他爲何存在呢?能夠從調用類方法開始提及:
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
複製代碼
上面這句代碼能夠正確執行,說明OC不只能夠給對象發送消息,也一樣能夠給類發送消息,由於上面咱們也提到了,OC中類也是一個對象。
那麼,當給類發送消息的時候,類對象的isa就指向了meta-class,因此要去meta-class中去找到該方法的實現。
能夠理解爲,meta-class的存在是爲了統一OC中全部對象消息查找轉發的流程,或者說是引入meta-class來保證不管是類仍是對象都能經過相同的機制查找方法的實現。
這裏又引出了幾個面試題:
問:對象的實例方法存在哪兒?類方法存在哪兒?
答:當一個對象的實例方法被調用時,會經過isa找到對應的類,而後在該類的class_data_bits_t中去查找方法對應的實現,因此實例方法是存在類中的,而類方法是存在元類中的。
問:類方法在元類中是以什麼形式存在?
答:類方法在元類中是以實例方法存在,而且,對象在類中是一個實例,類在元類中也是一個實例。因此,類的類方法,就是元類的實例方法。(這裏比較繞,多讀幾遍,好好理解)
繼續看源碼
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
typedef unsigned int uint32_t;
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
複製代碼
Cache的做用主要是爲了加速消息分發, 系統會對方法和對應的地址進行緩存,因此在實際運行中,大部分經常使用的方法都是會被緩存起來的,Runtime系統實際上很是快,接近直接執行內存地址的程序速度。
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
}
struct class_rw_t {
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;
char *demangledName;
}
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;
}
};
複製代碼
這兩部分本文不作過多的闡述,後面在寫消息查找轉發的時候在詳細介紹,這裏先簡單瞭解一下便可。
上面囉裏囉嗦的寫了一堆,看文章看到這裏可能也是似懂非懂的樣子,下面經過咱們最經常使用的幾個方法,來實戰一下上面講解的理論知識,幫助咱們理解記憶。
以下代碼打印結果是什麼?
@interface Sark : NSObject
複製代碼
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
複製代碼
打印結果:
14:17:56.444058+0800 test[30316:787077] Sark
14:17:56.444177+0800 test[30316:787077] Sark
複製代碼
解釋一下爲何NSLog(@"%@", NSStringFromClass([super class]));也輸出了Sark
- self 不必定是當前類, self只是一個形參 objc_msgSend(id self,SEL _cmd) 取決於消息的接收者
- 在調用[super class]的時候,runtime會去調用objc_msgSendSuper方法
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
複製代碼
在objc_msgSendSuper方法中,第一個參數是一個objc_super的結構體,這個結構體裏面有兩個變量,一個是接收消息的receiver,一個是當前類的父類super_class。
- objc_msgSendSuper的工做原理應該是這樣的: 從objc_super結構體指向的superClass父類的方法列表開始查找selector,找到後以objc->receiver去調用父類的這個selector。注意,最後的調用者是objc->receiver = self,而不是super_class!
以下代碼打印結果是什麼?
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);// yes no no no
複製代碼
查看isKindOfClass和isMemberOfClass的源碼:
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {// 第一次就獲取到了class -> meta class 而後再遍歷superclass
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {//第一次獲取了self -> class 而後再遍歷superclass
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
複製代碼
分析:
- isKindOfClass 區分實例方法和類方法的實現不一樣 類方法循環首先取了isa指針 第一次就獲取到了class -> meta class 而後再遍歷superclass 而實例方法是第一次獲取了self -> class 而後再遍歷superclass
- isMemberOfClass 沒有遍歷 直接比較
所以,第一行代碼 BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];的判斷流程是:
對NSObject meta-Class取super class用到裏圖裏紅圈標識出來的關係,獲得結果是NSObject
第二行代碼 BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];的分析流程是:
第三行代碼 BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]]; Sark取meta-Class而後再一直遍歷找super_class,最終也不會找到相等的,返回NO
第四行代碼 BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]]; Sark和Sark meta-Class不相等,返回NO
通過上面兩個小問題的實戰,是否是對對象、類、元類之間的關係有了更深入的理解,簡單總結一下:
- 每一個類都有一個惟一對應的元類
- 類對象的isa指向了元類
- 元類中以實例方法的形式保存了類的類方法
- 根元類(Root meta class)的isa指向本身,super class爲NSObject ,造成一個閉環