美文网首页
iOS进阶专项分析(三)、isa刨根问底

iOS进阶专项分析(三)、isa刨根问底

作者: 溪浣双鲤 | 来源:发表于2020-06-19 18:03 被阅读0次

    话不多说,直接开干

    一、从源码的角度了解isa及isa底层代码实现


    我们都知道Objective-C是一门面向对象的语言,所有的类都继承自NSObject, 既然这样,我们就从NSObject下手。

    进入runtime源码, 搜索OC的NSObject {, 找到NSObject的实现部分,

    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ROOT_CLASS
    OBJC_EXPORT
    @interface NSObject <NSObject> {
        Class isa  OBJC_ISA_AVAILABILITY;
    }
    
    

    我们在NSObject的定义部分找到了isa,并且这个isa是一个Class类型的,
    继续点击Class

    typedef struct objc_class *Class;
    
    

    发现Class其实是一个结构体objc_class的指针,打开这个结构体我们可以发现

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY; //isa指针
    #if !__OBJC2__
        Class _Nullable super_class  OBJC2_UNAVAILABLE;//父类
        const char * _Nonnull name    OBJC2_UNAVAILABLE;//类名
        long version                  OBJC2_UNAVAILABLE;//类的版本信息,默认0
        long info                    OBJC2_UNAVAILABLE;//类的信息,供运行期使用的一些位标识
        long instance_size            OBJC2_UNAVAILABLE;//该类的实例变量大小
        struct objc_ivar_list * _Nullable ivars  OBJC2_UNAVAILABLE;//该类的成员变量的链表         
        struct objc_method_list * _Nullable * _Nullable methodLists  OBJC2_UNAVAILABLE; //方法定义的链表
        struct objc_cache * _Nonnull cache                      OBJC2_UNAVAILABLE; //方法缓存
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE; //协议链表
    #endif
    } OBJC2_UNAVAILABLE;
    
    

    点击这个结构体,发现objc_class继承自objc_object

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() { 
            return bits.data();
        }
        
        ...
        
    }
    
    

    继续深入objc_object,找到了isa_t 类型的isa

    struct objc_object {
    private:
        isa_t isa;
    
    public:
    
        // ISA() assumes this is NOT a tagged pointer object
        Class ISA();
    
        // getIsa() allows this to be a tagged pointer object
        Class getIsa();
        ...
    };
    
    

    继续点击ISA()进入,我们来看一下isa的初始化

    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer()); 
    #if SUPPORT_INDEXED_ISA
        if (isa.nonpointer) {
            uintptr_t slot = isa.indexcls;
            return classForIndex((unsigned)slot);
        }
        return (Class)isa.bits;
    #else
        return (Class)(isa.bits & ISA_MASK);
    #endif
    }
    
    

    重点来了, 摘掉优化部分代码,isa的初始化代码就变成下面这样子

    inline Class 
    objc_object::ISA() 
    {
       return (Class)(isa.bits & ISA_MASK);
    }
    
    

    注意return这段代码,isa在初始化的时候会进行一个&运算,为什么要进行这样的一个运算呢?

    其实从64bit开始,isa需要进行一次位运算,才能计算出真实地址,这个位运算的对象就是ISA_MASK

    ISA_MASK的值不是唯一的,在arm64和X86架构下是不同的,我把源码中ISA_MASK的部分代码单独剪了出来

    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL                                                   # else
    #   
    # endif
    
    

    取到一个类的isa,然后和ISA_MASK进行一次位运算,就能够得到类对象的真实地址值。这个下面会做验证。

    那么源码分析到此为止,从源码分析,我们知道了isa其实就是一个指向类的结构体指针,也知道了isa是一个经过优化的指针。既然isa是指向类的指针,因为Objective—C的类有很多种,有的类之间也存在继承关系,那么isa在类中间的指向到底如何呢?那么下面我们开始验证isa的指向。

    二、控制变量法验证isa的指向

    还是新建工程,创建一个继承自NSObjectBMPerson类,并给BMPerson类添加三个属性,然后再创建一个BMStudent类继承自BMPerson,然后去ViewDidLoad中创建对象并给属性赋值,最后打印一下

    BMPerson * person = [[BMPerson alloc] init];
    person.name = @"张三";
    person.age = 18;
    person.height = 180;
        
    Class class1 = person.class;
    Class class2 = [BMPerson class];
    Class class3 = object_getClass(person);
        
    Class class4 = object_getClass(class1);
    
    NSLog(@"\n实例对象地址:%p\n类地址:class1:%p\n类地址:class2:%p\n类地址:class3:%p\n元类地址:class4:%p", person,class1, class2, class3, class4);
    
    
    BMStudent * student = [[BMStudent alloc] init];
    student.name = @"张三的学生";
    student.age = 18;
    student.height = 180;
        
    Class class5 = student.class;
    Class class6 = object_getClass(class5);
    NSLog(@"\nBMStudent\n实例对象地址:%p\n类地址:class1:%p\n元类地址:class4:%p", student,class5,class6);
    
    
    

    得到结果

    2020-06-19 16:55:42.996185+0800 TestProject[21084:1722006] 
    BMPerson
    实例对象地址:0x600002bdd1a0
    类地址:class1:0x1059b8798
    类地址:class2:0x1059b8798
    类地址:class3:0x1059b8798
    元类地址:class4:0x1059b8770
    
    
    2020-06-19 16:55:42.996416+0800 TestProject[21084:1722006] 
    BMStudent
    实例对象地址:0x600002b943c0
    类地址:class1:0x1059b86f8
    元类地址:class4:0x1059b86d0
    
    

    先来看上面这块log,分析BMPerson这块:
    此时BMPerson实例对象的地址是0x600002bdd1a0,class1、class2、class3指向的是同一个地址0x1059b8798,这也说明了类的地址只有一个,也就是说:类对象只有一个,而class4则不同,指向了0x1059b8770元类地址,为什么这么说呢,下面就是验证结果:

    1、验证实例对象的isa指向的是类的地址

    先拿到BMPerson实例的地址0x600002bdd1a0,然后查看内存

    (lldb) x 0x600002bdd1a0
    0x600002bdd1a0: 98 87 9b 05 01 00 00 00 12 00 00 00 b4 00 00 00  ................
    0x600002bdd1b0: 00 00 00 00 00 00 00 00 18 60 9b 05 01 00 00 00  .........`......
    
    

    我们知道前八位是isa,小端模式从后往前读八位得到 0x1059B8798,发现这个地址和上面打印的类的地址完全一致!如果感觉地址对比你不够清晰,可以直接po一下,发现这个isa就是指向了 BMPerson类,所以实例对象的isa指向的就是类结论成立。

    (lldb) x 0x600002bdd1a0
    0x600002bdd1a0: 98 87 9b 05 01 00 00 00 12 00 00 00 b4 00 00 00  ................
    0x600002bdd1b0: 00 00 00 00 00 00 00 00 18 60 9b 05 01 00 00 00  .........`......
    (lldb) po 0x01059B8798
    BMPerson
    
    

    上面有说,isa是一个经过优化的指针,我们把打印的类的地址和isa_mask进行 &操作,对比一下实际内存空间,发现是完全一致的

    BMPerson的实例对象person的内存空间

    (lldb) x 0x600002bdd1a0
    0x600002bdd1a0: 98 87 9b 05 01 00 00 00 12 00 00 00 b4 00 00 00  ................
    0x600002bdd1b0: 00 00 00 00 00 00 00 00 18 60 9b 05 01 00 00 00  .........`......
    
    
    (lldb) p/x (long)person->isa & 0x00007ffffffffff8ULL
    (unsigned long long) $16 = 0x00000001059b8798
    
    

    这就验证了:

    一个类对象的isa,然后和ISA_MASK进行一次位运算,就能够得这个类对象的真实地址值。
    

    2、验证类对象的isa指向的是元类的地址

    接下来我们读一下类地址的内存地址0x01059B8798

    (lldb) x 0x01059B8798
    0x1059b8798: 70 87 9b 05 01 00 00 00 00 ed c1 89 ff 7f 00 00  p...............
    0x1059b87a8: 80 f9 5f 00 00 60 00 00 07 00 00 00 2c 80 02 00  .._..`......,...
    
    

    把前八位也同样读取出来,得到0x01059B8770这个和我们最开始打印得到的元类地址也是完全一致,所以类对象的isa指向的就是元类的结论也成立。

    3、验证元类对象的isa指向的是根元类地址

    接下来我们对BMPerson的子类BMStudent也做同样的调试,得到的也是同样的结果,student实例的isa指向的就是BMStudent类的地址,BMStudent类的isa指向的就是BMStudent元类的地址。

    (lldb) po student
    <BMStudent: 0x600002b943c0>
    
    (lldb) x 0x600002b943c0
    0x600002b943c0: f8 86 9b 05 01 00 00 00 12 00 00 00 b4 00 00 00  ................
    0x600002b943d0: 00 00 00 00 00 00 00 00 58 60 9b 05 01 00 00 00  ........X`......
    (lldb) po 0x01059B86F8
    BMStudent
    
    (lldb) x 0x01059B86F8
    0x1059b86f8: d0 86 9b 05 01 00 00 00 98 87 9b 05 01 00 00 00  ................
    0x1059b8708: 00 0c 5e 00 00 60 00 00 07 00 00 00 2c 80 04 00  ..^..`......,...
    (lldb) 
    
    

    此时重点来了,我们分别得到了BMPerson元类的地址和BMStudent元类的地址,
    我们看一下这两个类的元类的isa指向的是什么!

    (lldb) x 0x1059b8770
    0x1059b8770: d8 ec c1 89 ff 7f 00 00 d8 ec c1 89 ff 7f 00 00  ................
    0x1059b8780: 80 6a 5f 00 00 60 00 00 07 00 00 00 35 c0 02 00  .j_..`......5...
    (lldb) x 0x1059b86d0
    0x1059b86d0: d8 ec c1 89 ff 7f 00 00 70 87 9b 05 01 00 00 00  ........p.......
    0x1059b86e0: 80 6b 5f 00 00 60 00 00 07 00 00 00 35 c0 01 00  .k_..`......5...
    
    

    我们发现了共同点!这两个元类的isa指向的是同一块内存空间,都是0x7FFF89C1ECD8, 我们暂且称这个类为未知类,我们再看这个未知类的内存地址!

    (lldb) x 0x7FFF89C1ECD8
    0x7fff89c1ecd8: d8 ec c1 89 ff 7f 00 00 00 ed c1 89 ff 7f 00 00  ................
    0x7fff89c1ece8: 00 60 7f 01 00 60 00 00 0f 00 00 00 31 c0 09 00  .`...`......1...
    
    

    打印之后发现,这个未知类的isa竟然指向的是自己!其实这个未知类我们现在有另一种称呼,叫做根元类

    再po一下这个地址

    (lldb) po 0x7FFF89C1ECD8
    NSObject
    
    

    妈耶,这个根元类的类型竟然是NSObject,重点来了!!!类型打印出来虽然是NSObject,但是这个地址代表的并不是NSObject类本身,而是NSObject元类的地址,我通过NSObject创建对象,然后获取这个对象的类,打印一下地址你就明白了

    (lldb) po 0x7FFF89C1ECD8
    NSObject
    
    
    (lldb) po NSLog(@"%p", [[[NSObject alloc] init] class]);
    2020-06-19 18:56:17.978190+0800 TextProject[21084:1722006] 0x7fff89c1ed00
    
    

    上面是获取到的根元类的地址,下面才是NSObject类的地址,这俩地址不相同就能证明,根元类其实就是NSObject这个类的元类!

    至此,isa的指向图已经很清楚了,如果我上面分析的这么多,其实思路就是按照这张图来分析的


    isa案例图解.png

    三、知识点总结


    1、**什么是isa

    isa是一个一个经过特殊处理优化过的指针,指向对象的类。
    
    实例对象的isa,指向它的类;
    类对象的isa,指向这个类对象对应的元类;
    元类对象的isa,指向根元类;
    根元类的isa指向的是自己;
    根元类是NSObject的元类
    
    

    2、什么是元类

    在Objective-C中,每当我们创建一个类,编译时就会自动创建一个元类(元类由系统进行维护),而我们创建的这个类就是这个元类的对象, 只不过这个元类只有一个实例,也就是说我们的 类对象只有一个!
    

    3、为什么元类这么设计?

    因为如果没有元类,则类对象的信息和实例对象的信息都会存储在类中,这样对于查找对
    应的信息,会更加复杂且混乱(如类方法和实例方法一样的情况下,需要区分查找对
    象的类型)

    元类和类的类型也必须一致!如果类型不一样,那类对象的信息和实例对象的信息又怎么去找呢?

    4、isa走向图

    isa走位图.png

    溪浣双鲤的技术摸爬滚打之路

    相关文章

      网友评论

          本文标题:iOS进阶专项分析(三)、isa刨根问底

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