iOS面试&笔试

Runtime底层原理之isa解读

2019-08-16  本文已影响59人  越来越胖了
Snip20190816_6.png

前一篇讲解了一下Runtime的底层原理,objc_msgSend的消息发送流程;其实学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针;在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址;从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。

共用体:

共用体把几种不同数据类型的变量存放在同一块内存里。共用体中的变量共享同一块内存。
union的主要特征有:

  • union中可以定义多个成员,union的大小由最大的成员的大小决定;
  • union成员共享同一块大小的内存,一次只能使用其中的一个成员;
  • 对union某一个成员赋值,会覆盖其他成员的值(但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节);
  • union量的存放顺序是所有成员都从低地址开始存放的。

isa的结构如下:

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#if SUPPORT_PACKED_ISA

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL        //这个很重要后面会讲到🙃
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
...下面代码省略不做重点介绍了🙃

};

isa参数详解

扯了这么多到底什么是isa❓官方介绍是这样的:
Every object is connected to the run-time system through itsisa instance variable, inherited from the NSObject class.isa identifies the object's class; it points to a structurethat's compiled from the class definition. Through isa, anobject can find whatever information it needs at run timesuch asits place in the inheritance hierarchy, the size and structure ofits instance variables, and the location of the methodimplementations it can perform in response to messages.
(每个对象都通过从NSObject类继承的实例变量itsisa连接到运行时系统。isa标识对象的类;它指向一个从类定义编译而来的结构。通过isa,一个对象可以在运行时找到它需要的任何信息,比如它在继承层次结构中的位置、它的实例变量的大小和结构,以及它在响应消息时可以执行的methodimplementations的位置。)

一个对象(Object)的isa指向了这个对象的类(Class),而这个对象的类(Class)的isa指向了metaclass。这样我们就可以找到静态方法和变量了。Objective-C的运行时是动态的,它能让你在运行时为类添加方法或者去除方法以及使用反射(反射什么鬼?传送门)。

讲isa就一定会提到metaclass,这里先提一下什么是metaclass❓

  • meta-class是一个类对象的类
  • 当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。
  • meta-class 是必须的,因为它为一个 Class 存储类方法。每个Class都必须有一个唯一的 meta-class,因为每个Class的类方法基本不可能完全相同。
isa.png

这张图描述如下:

为什么这里说生成两个 objc_class ,从前面metaclass就可以了解一二了。讲到这里,大家可能很疑惑isa到底是怎么指向类的,解释如下:isa里面存储各种信息,是一个共用体,其中shiftcls 33位才是用来存放地址,通过&ISA_MASK就可以将33位的地址值取出来,看图分析理解更透彻:

isa图解,来源于MJ

因为下面要用到class 讲解,我们先来看看 objc_class 的定义,然后来个实例和图解进行分析:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    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;

稍微解释一下各个参数的意思:

下面来个例子:

  1. 新建一个类Parent,继承于NSObject, 里面有成员方法-(void)selectorP,类方法+(void)ClassSelectorP。
  2. 新建一个类Child,继承于Parent,里面有成员方法-(void)selectorC, 类方法+(void)ClassSelectorC.

现在我们新建一个实例Child* child = [Chlid new];

  1. 当我们调用[child class] 的时候,child就会通过isa指针去找到Child。
  2. 当我们调用[child superclass]的时候,child 通过isa找到Child,再通过Child的isa,找到Parent。
    对象方法
  3. 接着,调用[child SelectorC],child通过isa找到Child,在Child的方法列表里面找到SelectorC;
  4. 再试着调用[child SelectorP],child通过isa找到Child,发现Child里面并没有这个方法,再通过Child的isa,找到Parent,在Parent里面的方法列表找到了SelectorP;
    类方法
  5. 再是类方法[Child ClassSelectorC],Child(请注意是类调用)通过isa找到Child的metaclass,在metaclass的方法列表里面找到了ClassSelectorC;
  6. 再试着调用[Child ClassSelectorP],Child通过isa找到Child的metaclass,发现metaclass里面并没有这个方法,通过metaclass里面的isa找到Parent的metaclass,在里面的方法列表找到了ClassSelectorP;
  7. 所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环
    图解如下:


    灵魂画手🙃

isa基本就讲完了,下面来个小李子再次加深理解:
NSArray *array = [[NSArray alloc] init];流程剖析

  1. [NSArray alloc]先被执行。先去NSArray中查找+alloc方法(类方法),因为NSArray没有+alloc方法,于是去父类NSObject去查找。
  2. 检测NSObject是否响应+alloc方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把isa指针指向NSArray类。同时,+alloc也被加进cache列表里面。
  3. 接着,执行-init方法,如果NSArray响应该方法,则直接将其加入cache;如果不响应,则去父类查找。
  4. 在后期的操作中,如果再以[[NSArray alloc] init]这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。

总结:

  • isa是一个共用体;isa标识对象的类;它指向一个从类定义编译而来的结构。通过isa,一个对象可以在运行时找到它需要的任何信息,比如它在继承层次结构中的位置、它的实例变量的大小和结构,以及它在响应消息时可以执行的imp的位置;
  • 也可以说isa是一个Class 类型的指针,每个实例对象有个isa的指针,他指向对象的类结构。
  • Objective-C 2.0中的描述是:新的对象被创建,其内存同时被分配,实例变量也同时被初始化;对象的第一个实例变量是一个指向 该对象的类结构的指针,叫做 isa。通过该指针,对象可以访问它对应的类以及相应的父类。

文章参考:
https://www.jianshu.com/p/cf93dc9d2262
https://www.jianshu.com/p/83b9f172c43c

上一篇 下一篇

猜你喜欢

热点阅读