美文网首页
第四篇:isa指针底层分析

第四篇:isa指针底层分析

作者: 坚持才会看到希望 | 来源:发表于2022-04-22 23:54 被阅读0次

    首先我们来看一段代码,通过这三种模式都可以打印HPWPerson的类对象,打印后我们发现三种方式打印的类对象地址都一样,

    void lgTestClassNum(void){
        Class class1 = [HPWPerson class];
        Class class2 = [HPWPerson alloc].class;
        Class class3 = object_getClass([HPWPerson alloc]);
        NSLog(@"\n%p-\n%p-\n%p",class1,class2,class3);
    }
    
    2022-04-22 18:16:13.838751+0800 002-isa分析[49376:528799] 
    0x1000082f0-
    0x1000082f0-
    0x1000082f0
    

    接着我们打开objc的源码,通过搜索struct objc_class发现了typedef struct objc_class *Class,发现类对象其实就是一个结构体,接着我们再去源码搜索下objc_class,会发现什么呢?

    WechatIMG1918.jpeg

    下面这个是objc_class继承objc_object这个,同时我们看到了ISA指针,说明类对象里也有isa指针,类对象的isa指针是指向元类对象的。那我们怎么去证明呢?且元类又是什么东西呢?

    struct objc_class : objc_object {
      objc_class(const objc_class&) = delete;
      objc_class(objc_class&&) = delete;
      void operator=(const objc_class&) = delete;
      void operator=(objc_class&&) = delete;
        // Class ISA; // 8
        Class superclass;// 8
        cache_t cache;   // 16         // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags //
    
    

    带着上面的问题,我们继续去探索,我们打印下对象,然后通过对象地址找到对应的类对象,其中用到了ISA_BIT这个掩码的值,这个掩码我们需要选对,因为我们用的电脑是M1的所以需要选择对应的arm64架构的,所以ISA_BIT对应的掩码值也就是0x007ffffffffffff8ULL.

       HPWPerson *p = [HPWPerson alloc];
       NSLog(@"%@",p);
    
    2022-04-22 18:36:58.073715+0800 002-isa分析[50359:546896] <HPWPerson: 0x10121d3d0>
    (lldb) x/4gx p
    0x10121d3d0: 0x01000001000082f1 0x0000000000000000
    0x10121d3e0: 0x0000000000000000 0x0000000000000000
    (lldb) 
    
    # if __arm64__
    // ARM64 simulators have a larger address space, so use the ARM64e
    // scheme even when simulators build for ARM64-not-e.
    #   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
    #     define ISA_MASK        0x007ffffffffffff8ULL
    #     define ISA_MAGIC_MASK  0x0000000000000001ULL
    #     define ISA_MAGIC_VALUE 0x0000000000000001ULL
    #     define ISA_HAS_CXX_DTOR_BIT 0
    #     define ISA_BITFIELD                                                      \
            uintptr_t nonpointer        : 1;                                       \
            uintptr_t has_assoc         : 1;                                       \
            uintptr_t weakly_referenced : 1;                                       \
            uintptr_t shiftcls_and_sig  : 52;                                      \
            uintptr_t has_sidetable_rc  : 1;                                       \
            uintptr_t extra_rc          : 8
    #     define RC_ONE   (1ULL<<56)
    #     define RC_HALF  (1ULL<<7)
    

    接着我们把得到掩码和打印的HPWPerson对象的地址进行一个与操作,等到一个内存地址.然后我们再po下这个地址,就会得到一个HPWPerson类对象,接着我们再打印类对象的x/4gx的地址,0x00000001000082c8是类对象的isa指针地址,然后我们把类对象的isa指针与掩码再与一下,接着po下得到的地址就会发现打印的还是HPWPerson。

    2022-04-22 18:36:58.073715+0800 002-isa分析[50359:546896] <HPWPerson: 0x10121d3d0>
    (lldb) x/4gx p
    0x10121d3d0: 0x01000001000082f1 0x0000000000000000
    0x10121d3e0: 0x0000000000000000 0x0000000000000000
    
    (lldb) p/x 0x01000001000082f1 & 0x007ffffffffffff8ULL
    (unsigned long long) $1 = 0x00000001000082f0
    
    (lldb) po 0x00000001000082f0
    HPWPerson
    
    (lldb) x/4gx 0x00000001000082f0
    0x1000082f0: 0x00000001000082c8 0x00000001e72881c8
    0x100008300: 0x0007000100733280 0x0002802900000000
    
    (lldb) p/x 0x00000001000082c8 & 0x007ffffffffffff8ULL
    (unsigned long long) $4 = 0x00000001000082c8
    
    (lldb) po 0x00000001000082c8
    HPWPerson
    

    通过上面的打印,我们发现HPWPerson的两个地址是不一样的,但是我们之前是知道类对象只有一个地址,那到底哪个是对的呢,我继续去探究下,我们在最开始打印的地址为0x1000082f0,所以我们第一次得到的HPWPerson类的地址是一样的。那0x00000001000082c8对应的HPWPerson是什么呢,其实他是一个元类。那么发现HPWPerson元类和HPWPerson类对象的名字都是一样的。总结是:实例对象的isa-->类对象isa-->元类对象,其实元类对象也有一个isa指针,上面我们得到元类的地址,那我们继续打印,这里得到一个NSObject

    (lldb) x/4gx 0x00000001000082c8
    0x1000082c8: 0x00000001e72881a0 0x00000001e72881a0
    0x1000082d8: 0x0003000100733900 0x0001e03500000000
    
    (lldb) p/x 0x00000001e72881a0 & 0x007ffffffffffff8ULL
    (unsigned long long) $6 = 0x00000001e72881a0
    
    (lldb) po 0x00000001e72881a0
    NSObject
    

    接着我们来打印下NSObject的类方法,也就是NSObject.class,最后得到的0x00000001e72881a0就是根元类,到了这里我们就知道了:
    实例对象的isa-->类对象isa-->元类对象isa-->根元类
    根类的NSObject isa -- >根元类(NSObject 元类)

    (lldb) p/x NSObject.class
    (Class) $8 = 0x00000001e72881c8 NSObject
    
    (lldb) x/4gx 0x00000001e72881c8
    0x1e72881c8: 0x00000001e72881a0 0x0000000000000000
    0x1e72881d8: 0x000100010070fa60 0x0002801000000000
    
    (lldb) p/x 0x00000001e72881a0 & 0x007ffffffffffff8ULL
    (unsigned long long) $9 = 0x00000001e72881a0
    

    通过上面我们有个疑问?根元类的isa指针指向什么呢,我们经过下面的输出得到了内存地址还是相同,都是0x00000001e72881a0,所以我们得出的结论是:

    实例对象的isa-->类对象isa-->元类对象isa-->根元类isa -->根元类自己 ,这样就实现了一个指向的闭环。

    (lldb) x/4gx 0x00000001e72881a0
    
    0x1e72881a0: 0x00000001e72881a0 0x00000001e72881c8
    0x1e72881b0: 0x0007000100733b90 0x0001e03400000000
    (lldb) p/x 0x00000001e72881a0 & 0x007ffffffffffff8ULL
    (unsigned long long) $10 = 0x00000001e72881a0
    
    (lldb) po 0x00000001e72881a0
    

    接着我们用代码进行分析一下:

      // NSObject实例对象
        NSObject *object1 = [NSObject alloc];
        // NSObject类对象
        Class class = object_getClass(object1);
        // NSObject元类(根元类)
        Class metaClass = object_getClass(class);
        
        NSLog(@"NSObject实例对象:%p",object1);
        NSLog(@"NSObject类对象:%p",class);
        NSLog(@"NSObject元类(根元类):%p",metaClass);
        
        // HPWPerson  -- 元类的父类就是父类的元类
        Class pMetaClass = objc_getMetaClass("HPWPerson");
        Class psuperClass = class_getSuperclass(pMetaClass);
        NSLog(@"%@ - %p",pMetaClass,pMetaClass);
        NSLog(@"%@ - %p",psuperClass,psuperClass);
        
        // HPWTeacher继承自HPWPerson
        // HPWTeacher元类的父类 就是 HPWPerson(HPWPerson的元类)
        Class tMetaClass = objc_getMetaClass("HPWTeacher");
        Class tsuperClass = class_getSuperclass(tMetaClass);
        NSLog(@"%@ - %p",tsuperClass,tsuperClass);
        
        // NSObject的父类
        Class nsuperClass = class_getSuperclass(NSObject.class);
        NSLog(@"%@ - %p",nsuperClass,nsuperClass);
        
        // 根元类的父类 -- NSObject
        Class rnsuperClass = class_getSuperclass(metaClass);
        NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
    

    运行上面的语句后打印出如下:

    2022-04-22 22:39:16.162213+0800 002-isa分析[53384:683254] NSObject实例对象:0x101011f10
    2022-04-22 22:39:16.162244+0800 002-isa分析[53384:683254] NSObject类对象:0x1e72881c8
    2022-04-22 22:39:16.162263+0800 002-isa分析[53384:683254] NSObject元类(根元类):0x1e72881a0
    2022-04-22 22:39:16.162326+0800 002-isa分析[53384:683254] HPWPerson - 0x1000082c8
    2022-04-22 22:39:16.162344+0800 002-isa分析[53384:683254] NSObject - 0x1e72881a0
    2022-04-22 22:39:16.162356+0800 002-isa分析[53384:683254] HPWPerson - 0x1000082c8
    2022-04-22 22:39:16.162369+0800 002-isa分析[53384:683254] (null) - 0x0
    2022-04-22 22:39:16.162417+0800 002-isa分析[53384:683254] NSObject - 0x1e72881c8
    

    通过上面我们知道,NSObject的父类是nil,根元类的父类就是NSObject,最后我们用两张图进行总结下,方便大家进行理解。


    WechatIMG1923.jpeg WechatIMG1924.jpeg

    上面我们知道,NSObject是万类之主,isa指针指向都是可以把子类通过3步找到根元类。

    知识点补充:内存平移

    通过下面代码打印:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSLog(@"Hello World!");
                
            HPWPerson *p = [HPWPerson alloc];
            
        }
        return 0;
    }
    
    
    WechatIMG1925.jpeg

    上面的0x100008198是类对象的首地址

    我们再看下源码:

    struct objc_class : objc_object {
      objc_class(const objc_class&) = delete;
      objc_class(objc_class&&) = delete;
      void operator=(const objc_class&) = delete;
      void operator=(objc_class&&) = delete;
        // Class ISA; // 8
        Class superclass;// 8
        cache_t cache;   // 16         // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags //
    
        Class getSuperclass() const {
    #if __has_feature(ptrauth_calls)
    #   if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
            if (superclass == Nil)
                return Nil;
    
    #if SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL
            void *stripped = ptrauth_strip((void *)superclass, ISA_SIGNING_KEY);
            if ((void *)superclass == stripped) {
                void *resigned = ptrauth_sign_unauthenticated(stripped, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
                if ((void *)superclass != resigned)
                    return Nil;
            }
    #endif
    
    

    我们来分析下 class_data_bits_t bits ,分析这个我们用到了内存平移的概念。进入到class_data_bits_t里我们看到了有method_t这个方法,我们关系的是 SEL name;和 MethodListIMP imp;,同时还有个isSmall和big这个概念,这个就是大小端的概念,大小端的意思我们用一张图来表示,大小端是指在不同的处理器上不同,我们用的是M1电脑,所以是个小端。


    WechatIMG1926.jpeg
    struct method_t {
        static const uint32_t smallMethodListFlag = 0x80000000;
    
        method_t(const method_t &other) = delete;
    
        // The representation of a "big" method. This is the traditional
        // representation of three pointers storing the selector, types
        // and implementation.
        struct big {
            SEL name;
            const char *types;
            MethodListIMP imp;
        };
    
    private:
        bool isSmall() const {
            return ((uintptr_t)this & 1) == 1;
        }
    
        // The representation of a "small" method. This stores three
        // relative offsets to the name, types, and implementation.
        struct small {
            // The name field either refers to a selector (in the shared
            // cache) or a selref (everywhere else).
            RelativePointer<const void *> name;
            RelativePointer<const char *> types;
            RelativePointer<IMP, /*isNullable*/false> imp;
    
            bool inSharedCache() const {
                return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                        objc::inSharedCache((uintptr_t)this));
            }
        };
    

    然后我们通过P命令去打印,会得到这个我们关心的 SEL方法的 name,也就得到了结论(method_t里存放的是HPWPerson里的所有的方法)。类方法不存在method_t里,实例方法存在method_t里。其实类方法是存放在元类里的。属性存放在property_t这个结构体里,打印其属性后其有name显示属性名字,name = ‘age’,还有一个attributes = “jihih”用来进行描述的。

    相关文章

      网友评论

          本文标题:第四篇:isa指针底层分析

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