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

logo

本系列博客是本人的源碼閱讀筆記吩愧,如果有 iOS 開發(fā)者在看 runtime 的贡耽,歡迎大家多多交流。

分析

之前的
iOS開發(fā)之 runtime(28) :獲取每個(gè) 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

面對(duì)上面一些代碼将谊,相信大部分朋友結(jié)合筆者之前的文章應(yīng)該能看懂的,但如果您仍有疑問的院刁,本文就給大家再次深入分析一下以上代碼的作用糯钙,并做一些 Demo 以便大家更深的理解。

分析

其實(shí)以上代碼很簡(jiǎn)單,分兩部分超营,
第一部分是一些結(jié)構(gòu)體的聲明鸳玩,筆者在所有的結(jié)構(gòu)體后面都加了 1 這個(gè)數(shù)字,用于和系統(tǒng)的區(qū)分演闭。這一這么說,筆者聲明的這些結(jié)構(gòu)體颓帝,除了名字米碰,其他都和系統(tǒng)的一模一樣。
第二部分是 main 函數(shù)购城,熟悉的朋友也很容易理解吕座,無非是從 section 為 __objc_classlist 內(nèi)取出所有的類,然后調(diào)用其 mangledName 方法瘪板。只是 mangledName 方法比較有意思:

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

也就是說吴趴,他獲取的是 data() 方法 中的 name 字段,繼續(xù)侮攀,我們看一下 data 方法來自何處:
data 方法是獲取的結(jié)構(gòu)體 bits 中的 name 字段锣枝。而且 bits & FAST_DATA_MASK 獲取的居然是結(jié)構(gòu)體 class_ro_t1 !相信讀者已經(jīng)開始?xì)g呼了兰英,因?yàn)檎f明了以下幾點(diǎn):

  1. 一個(gè)完整的類其實(shí)有 readonly 結(jié)構(gòu)體 和 readwrite 結(jié)構(gòu)體構(gòu)成
  2. readonly 通過獲取 bits & FAST_DATA_MASK 獲得 readwrite 部分撇叁。

實(shí)戰(zhàn)

實(shí)戰(zhàn)部分的代碼在這里:
https://github.com/zjh171/RuntimeSample/tree/master/MyDIYClass/MyDIYClass2

完整的代碼不貼了,這里只貼幾個(gè)不一樣的部分:

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

這里幾個(gè)例子都是從 readwrite 里取出的數(shù)據(jù)來和一些常數(shù)進(jìn)行 & 操作從而判斷是否為真/假畦贸,甚至從里面獲取某些數(shù)據(jù)陨闹。 關(guān)于 & 操作這里不多做介紹了,相信經(jīng)常讀筆者專欄的朋友應(yīng)該很熟悉了薄坏。

另外趋厉,筆者的測(cè)試類代碼如下:

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

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

這里給大家打印出結(jié)果:

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

這里我們一個(gè)一個(gè)作解釋:

  1. 很顯然,只要不是 NSObject 胶坠,那肯定不是 root class
  2. super class 獲取不多君账,暫時(shí)不知道為什么
  3. 從上幾篇文章中我們可知,剛從 section 里取出來的數(shù)據(jù)肯定是 not Realized
  4. 未使用 ARC涵但,因?yàn)楣P者做了如下設(shè)置:
禁止 ARC
  1. load 方法是否存在杈绸。筆者做了個(gè)實(shí)驗(yàn),如果在 TestObject 中 添加 load 方法矮瘟,那就 loadMethod has Called瞳脓,否則 loadMethod has not Called

  2. 判斷是否有 C++ 構(gòu)造函數(shù)。為了證明這個(gè)值澈侠,筆者在頭文件添加了一個(gè)類 A劫侧,后來筆者做了實(shí)驗(yàn),如果 調(diào)用了 A 里面的方法,或者在 TestObject 中聲明了 A 對(duì)象烧栋,則這個(gè)結(jié)果會(huì)變成 has HAS_CXX_CTOR

  3. 用于判斷写妥,在非 ARC 的情況下,有沒有使用 ARC 的特性审姓,比如筆者注釋的

 __weak NSObject *propertyA;

使用的話就是 true珍特,否則 false

注意

注意!注意魔吐!特別注意扎筒!
如果前面我們寫了 load 方法,會(huì)影響后面所有的結(jié)果酬姆,這是為什么呢嗜桌?筆者后面的文章會(huì)給大家解釋,歡迎大家繼續(xù)關(guān)注筆者博客辞色。

總結(jié)

筆者在寫博客的過程中也經(jīng)常有豁然開朗的感覺骨宠,也許這就是 runtime 的魅力吧。有人疑問筆者相满,做這個(gè) runtime 分析有什么用层亿,筆者付之一笑。因?yàn)?runtime 中真的有太多可以提升你開發(fā)能力的東西雳灵,讓你網(wǎng)上層幫你的業(yè)務(wù)代碼更少 bug棕所,更優(yōu)秀設(shè)計(jì);往下層悯辙,提升你對(duì)整個(gè) iOS 系統(tǒng)有更深的理解琳省。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躲撰,隨后出現(xiàn)的幾起案子针贬,更是在濱河造成了極大的恐慌,老刑警劉巖拢蛋,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桦他,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谆棱,警方通過查閱死者的電腦和手機(jī)快压,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垃瞧,“玉大人蔫劣,你說我怎么就攤上這事「龃樱” “怎么了脉幢?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵歪沃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我嫌松,道長(zhǎng)沪曙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任萎羔,我火速辦了婚禮液走,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贾陷。我一直安慰自己育灸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布昵宇。 她就那樣靜靜地躺著,像睡著了一般儿子。 火紅的嫁衣襯著肌膚如雪瓦哎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天柔逼,我揣著相機(jī)與錄音蒋譬,去河邊找鬼。 笑死愉适,一個(gè)胖子當(dāng)著我的面吹牛犯助,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播维咸,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼剂买,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了癌蓖?” 一聲冷哼從身側(cè)響起瞬哼,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎租副,沒想到半個(gè)月后坐慰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡用僧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年结胀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片责循。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡糟港,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沼死,到底是詐尸還是另有隱情着逐,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站耸别,受9級(jí)特大地震影響健芭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秀姐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一慈迈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧省有,春花似錦痒留、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舷蟀,卻和暖如春恤磷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背野宜。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工扫步, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匈子。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓河胎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親虎敦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子游岳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345