Runtime之 Class 結構的剖析

從源碼objc_runtime-new.h 中可以看到

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;  //方法緩存         
    class_data_bits_t bits;  //用於獲取具體的類信息
    class_rw_t *data() { 

        return bits.data();

    }
 }

// 我們來看 bits*data 這個方法的實現

  class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
// bits & FAST_DATA_MASK 返回一個class_rw_t 的結構體指針

接着我們來窺探下 class_rw_t 都存儲來哪些東西?

class_rw_t裏面的methodspropertiesprotocols是二維數組,是可讀可寫的,包含了類的初始內容、分類的內容

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;
 }

//class_ro_t  如下

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; //instance對象的size
#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;
}

 

class_ro_t裏面的baseMethodListbaseProtocolsivarsbaseProperties是一維數組,是隻讀的,包含了類的初始內容

然後我來在來看看method_t 吧。

struct method_t {
    SEL name; //方法選擇器(方法名)
    const char *types; //編碼(返回值類型,參數類型)
/*
    ios提供了一個@encode的指令,可以將具體的類型變成字符串編碼。
    假如有這樣一個方法:
    -(int)test:(int)Age Height:(CGFloat)height;
    type 爲:i24@:08i16f20
    其中i爲返回值類型,24 所有參數的字節總數(id,SEL,int,float),@是id,:代表SEL,i參數int,f參數float.另外的數字代表參數 開始的字節位數
*/
    IMP imp;//方法的實現
}

SEL代表方法\函數名,一般叫做選擇器,底層結構跟char *類似,想要驗證也很簡單。如下:

char *str = "init";
NSLog(@"%s-%@",str,NSStringFromSelector(@selector(init)));

//打印如下
2018-10-31 15:52:10.218499+0800 關聯對象[3274:207551] init-init

可以通過sel_getName()NSStringFromSelector()轉成字符串

不同類中相同名字的方法,所對應的方法選擇器是相同的

繼續往下看:Class內部結構中有個方法緩存(cache_t),用散列表(哈希表)來緩存曾經調用過的方法,可以提高方法的查找速度。(很重要)

struct cache_t {
    struct bucket_t *_buckets; //散列表 
    mask_t _mask; //散列表的長度-1
    mask_t _occupied;//已經緩存的方法數量
}
struct bucket_t {
private:
    cache_key_t _key; // SEL作爲key
    IMP _imp;   //函數的內存地址,可理解爲value
}
//簡述下原理:對象調用方法是通過isa 是找到類對象,
然後在類對象通過方法緩存去找到對應的方法緩存列表_buckets,
一旦發現調用的SEL與緩存列表中的key 相同,就指出取出函數的地址,
通過地址直接調用函數方法。

原理如下:

0 NULL
1 NULL
2 NULL
4 bucket_t (_key=@select(test),_imp)
...... .....

假如散列表如上,對象的方法名叫test。他會拿對象方法的地址 & _mask(上面介紹過了是散列表的長度-1) 假如爲4,他就會去座標爲4的散列表找方法,(直接去找座標爲4的方法列表,前面的列表位置留空)假如方法存在直接調用,假如不存在就直接緩存進去。假如兩個不同的地址值 & _mask得出來的索引值相同,那會怎麼做吶?來看下源碼

bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(k, m);
/*
static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
{
    return (mask_t)(key & mask);
}
*/
    mask_t i = begin;
    do {
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
/*
cache_next 的實現
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
*/
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}

發現相同之後,吧得到的下標-1 緩存到得到的下標裏去。如果直接減到0的時候直接賦值mask,然後在-1 慢慢的找,直到找到空餘的位置緩存。以空間換時間。大家可以自己參考objc_cache.mm文件中。

//一個類的內部結構-供參考:一個c++文件
//  XZClassInfo.h
//  TestClass


#import <Foundation/Foundation.h>

#ifndef XZClassInfo_h
#define XZClassInfo_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;

#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}

#else
#error unknown architecture
#endif

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    
    IMP imp(SEL selector)
    {
        mask_t begin = _mask & (long long)selector;
        mask_t i = begin;
        do {
            if (_buckets[i]._key == 0  ||  _buckets[i]._key == (long long)selector) {
                return _buckets[i]._imp;
            }
        } while ((i = cache_next(i, _mask)) != begin);
        return NULL;
    }
};

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() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC對象 */
struct mj_objc_object {
    void *isa;
};

/* 類對象 */
struct mj_objc_class : mj_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    mj_objc_class* metaClass() {
        return (mj_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* XZClassInfo_h */

用法如下:

        mj_objc_class *goodStuderntClass = (__bridge mj_objc_class *)[XZGoodStudent class];

        cache_t cache = goodStuderntClass->cache;

        bucket_t *buckets = goodStuderntClass->cache._buckets;

        bucket_t bucket =buckets [(long long)@selector(goodStudentTest) & cache._mask];

        NSLog(@"%s",bucket._key);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章