美文网首页iOS开发之深入理解runtimeios runtime专题runtime
iOS开发之 runtime(33) :获取每个 class 信

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

作者: kyson老师 | 来源:发表于2019-06-27 23:03 被阅读3次
    logo

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

    相关文章

      网友评论

        本文标题:iOS开发之 runtime(33) :获取每个 class 信

        本文链接:https://www.haomeiwen.com/subject/qqtjcctx.html