iOS开发之 runtime(33) :获取每个 class 信息(2)

logo

本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 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,因为笔者做了如下设置:
禁止 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 系统有更深的理解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268