iOS開發之 runtime(33) :獲取每個 class 信息(2)

本系列博客是本人的源碼閱讀筆記,如果有 iOS 開發者在看 runtime 的,歡迎大家多多交流。爲了方便討論,本人新建了一個微信羣(iOS技術討論羣),想要加入的,請添加本人微信:zhujinhui207407,【加我前請備註:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,歡迎一起討論

本文完整版詳見筆者小專欄:https://xiaozhuanlan.com/runtime

分析

之前的
iOS開發之 runtime(28) :獲取每個 class 信息(1)
裏面,我們寫過如下一些代碼:

#import <dlfcn.h>
#include <mach-o/loader.h>
#include <mach-o/getsect.h>

struct class_ro_t1 {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;
};

struct class_rw_t1 {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    const class_ro_t1 *ro;
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#if !__LP64__
#define FAST_DATA_MASK        0xfffffffcUL
#elif 1
#define FAST_DATA_MASK          0x00007ffffffffff8UL
#else
#define FAST_DATA_MASK          0x00007ffffffffff8UL
#endif

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit)
    {
        return bits & bit;
    }

public:

    class_rw_t1* data() {
        return (class_rw_t1 *)(bits & FAST_DATA_MASK);
    }

};


#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

struct cache_t1 {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;

public:
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();

    mask_t capacity();
    bool isConstantEmptyCache();
    bool canBeFreed();

    static size_t bytesForCapacity(uint32_t cap);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);

    void expand();
    void reallocate(mask_t oldCapacity, mask_t newCapacity);
    //    struct bucket_t * find(cache_key_t key, id receiver);
    //
    static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};

struct objc_class1 : objc_object {
    // Class ISA;
    Class superclass;
    cache_t1 cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t1 *data() {
        return bits.data();
    }

    const char *mangledName() {
        return ((const class_ro_t1 *)data())->name;
    }

    const char *demangledName(bool realize = false);
    const char *nameForLogging();
};

typedef struct objc_class1 *Class1;
typedef struct classref * classref_t;


#ifndef __LP64__
#define mach_header mach_header
#else
#define mach_header mach_header_64
#endif


const struct mach_header *machHeader = NULL;
static NSString *configuration = @"";

int main()
{
    unsigned long byteCount = 0;

    if (machHeader == NULL)
    {
        Dl_info info;
        dladdr((__bridge const void *)(configuration), &info);
        machHeader = (struct mach_header_64*)info.dli_fbase;
    }

    uintptr_t* data = (uintptr_t *) getsectiondata(machHeader, "__DATA", "__objc_classlist", &byteCount);

    NSUInteger counter = byteCount/sizeof(void*);

    for(NSUInteger idx = 0; idx < counter; ++idx)
    {
        Class1 cls =(Class1)( data[idx]);
        NSLog(@"class:%s",cls->mangledName());
    }

    return 0;
}

這些代碼筆者也放到 github 中給大家參考了:
https://github.com/zjh171/RuntimeSample/tree/master/MyDIYClass

面對上面一些代碼,相信大部分朋友結合筆者之前的文章應該能看懂的,但如果您仍有疑問的,本文就給大家再次深入分析一下以上代碼的作用,並做一些 Demo 以便大家更深的理解。

分析

其實以上代碼很簡單,分兩部分,
第一部分是一些結構體的聲明,筆者在所有的結構體後面都加了 1 這個數字,用於和系統的區分。這一這麼說,筆者聲明的這些結構體,除了名字,其他都和系統的一模一樣。
第二部分是 main 函數,熟悉的朋友也很容易理解,無非是從 section 爲 __objc_classlist 內取出所有的類,然後調用其 mangledName 方法。只是 mangledName 方法比較有意思:

const char *mangledName() {
    return ((const class_ro_t1 *)data())->name;
}

也就是說,他獲取的是 data() 方法 中的 name 字段,繼續,我們看一下 data 方法來自何處:
data 方法是獲取的結構體 bits 中的 name 字段。而且 bits & FAST_DATA_MASK 獲取的居然是結構體 class_ro_t1 !相信讀者已經開始歡呼了,因爲說明了以下幾點:

  1. 一個完整的類其實有 readonly 結構體 和 readwrite 結構體構成
  2. readonly 通過獲取 bits & FAST_DATA_MASK 獲得 readwrite 部分。

實戰

實戰部分的代碼在這裏:
https://github.com/zjh171/RuntimeSample/tree/master/MyDIYClass/MyDIYClass2

完整的代碼不貼了,這裏只貼幾個不一樣的部分:

for(NSUInteger idx = 0; idx < counter; ++idx)
{
    Class1 cls =(Class1)( data[idx]);
    NSLog(@"class:%s \n",cls->mangledName1());
    
    bool isRoot = (cls->data()->flags & RO_ROOT);
    if (isRoot) {
        printf("is root \n");
    } else {
        printf("is not root \n");
        
        Class1 superClass = cls->superclass;
        printf("superClass:%s \n",superClass->mangledName1());
    }
    
    bool isRealized = cls->data()->flags & RW_REALIZED;
    if (isRealized) {
        printf("is isRealized\n");
    } else {
        printf("is not Realized\n");
    }
    
    bool isARC = cls->data()->flags & RO_IS_ARC;
    if (isARC) {
        printf("is arc\n");
    } else {
        printf("is  not arc\n");
    }
    
    
    
    bool loadMethodHasCalled = cls->data()->flags & RW_LOADED;
    if (loadMethodHasCalled) {
        printf("loadMethod Has Called \n");
    } else {
        printf("loadMethod has not Called \n");
    }
    
    bool hasCXXCtor = cls->data()->flags & RO_HAS_CXX_STRUCTORS;
    if (hasCXXCtor) {
        printf(" Has HAS_CXX_CTOR \n");
    } else {
        printf(" has not HAS_CXX_CTOR \n");
    }
    
    bool hasWeakWithoutARC = cls->data()->flags & RO_HAS_WEAK_WITHOUT_ARC;
    if (hasWeakWithoutARC) {
        printf(" Has RO_HAS_WEAK_WITHOUT_ARC \n");
    } else {
        printf(" has not RO_HAS_WEAK_WITHOUT_ARC \n");
    }
    
}

這裏幾個例子都是從 readwrite 裏取出的數據來和一些常數進行 & 操作從而判斷是否爲真/假,甚至從裏面獲取某些數據。 關於 & 操作這裏不多做介紹了,相信經常讀筆者專欄的朋友應該很熟悉了。

另外,筆者的測試類代碼如下:

class A
{
public:
    //默認構造函數
    A()
    {
        num=1001;
        age=18;
    }
    //初始化構造函數
    A(int n,int a):num(n),age(a){}
private:
    int num;
    int age;
};

@interface TestObject : NSObject {
//    __weak NSObject *propertyA;
//    A a;
}

這裏給大家打印出結果:

is not root 
superClass: 
is not Realized
is  not arc
loadMethod has not Called 
 has not HAS_CXX_CTOR 
 has not RO_HAS_WEAK_WITHOUT_ARC 
Program ended with exit code: 0

這裏我們一個一個作解釋:

  1. 很顯然,只要不是 NSObject ,那肯定不是 root class
  2. super class 獲取不多,暫時不知道爲什麼
  3. 從上幾篇文章中我們可知,剛從 section 裏取出來的數據肯定是 not Realized
  4. 未使用 ARC,因爲筆者做了如下設置:
  1. load 方法是否存在。筆者做了個實驗,如果在 TestObject 中 添加 load 方法,那就 loadMethod has Called,否則 loadMethod has not Called

  2. 判斷是否有 C++ 構造函數。爲了證明這個值,筆者在頭文件添加了一個類 A,後來筆者做了實驗,如果 調用了 A 裏面的方法,或者在 TestObject 中聲明瞭 A 對象,則這個結果會變成 has HAS_CXX_CTOR

  3. 用於判斷,在非 ARC 的情況下,有沒有使用 ARC 的特性,比如筆者註釋的

 __weak NSObject *propertyA;

使用的話就是 true,否則 false

注意

注意!注意!特別注意!
如果前面我們寫了 load 方法,會影響後面所有的結果,這是爲什麼呢?筆者後面的文章會給大家解釋,歡迎大家繼續關注筆者博客。

總結

筆者在寫博客的過程中也經常有豁然開朗的感覺,也許這就是 runtime 的魅力吧。有人疑問筆者,做這個 runtime 分析有什麼用,筆者付之一笑。因爲 runtime 中真的有太多可以提升你開發能力的東西,讓你網上層幫你的業務代碼更少 bug,更優秀設計;往下層,提升你對整個 iOS 系統有更深的理解。

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