Runtime底层原理之isa解读

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参数详解

  • nonpointer:
    0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
    1,代表优化过,使用位域存储更多的信息
  • has_assoc:是否有设置过关联对象,如果没有,释放时会更快
  • has_cxx_dtor:是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
  • shiftcls:存储着Class、Meta-Class对象的内存地址信息
  • magic:用于在调试时分辨对象是否未完成初始化
  • weakly_referenced:是否有被弱引用指向过,如果没有,释放时会更快
  • deallocating:对象是否正在释放
  • extra_rc:表示该对象的引用计数值,实际上是引用计数值减 1,例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10,则需要使用到下面的 has_sidetable_rc。
  • has_sidetable_rc:当对象引用计数大于 10 时,则has_sidetable_rc 的值为 1,那么引用计数会存储在一个叫 SideTable 的类的属性中,这是一个散列表。原文链接

扯了这么多到底什么是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的类方法基本不可能完全相同。

问题提出:iOS中有静态方法与动态方法,那么两种方法的异同是什么?(问题来源:脚本之家-lqh )

因为每个对象都由相应的数据结构与方法相构成,一个程序可能有多个属于同一个类的对象,而每个对象的数据结构应该是不一的,但方法是相同的,若为每个对象开辟内存空间来存储方法,必然是对内存空间极大的浪费。因此apple是通过类对象与元类来解决这个问题的。

对象的底层实际上就是结构体,其有两个重要的指针,一个是isa指针,一个是super指针。
isa指针:负责指向类对象,用来表明自己是什么类类型,并能调用类对象中的动态方法。
super指针:表示对象的继承关系,指向父类,从而能调用父类的相应方法。
类对象:类对象是由元类生成的对象,负责存储动态方法,动态方法在编译器是不确定的,因此编译器也无法检测与动态方法相关的错误,动态方法的调用是在运行期中通过消息机制来执行的,因此也只有运行期才会报错。


image.png

结论:

两者的差异包括:

  1. 方法列表是区分开的,分别存储在类对象与元类中。
  2. 动态方法是在运行期中调用,编译器无法检测错误,静态方法是在编译器就确定,编译器能检测错误。
  3. 动态方法由对象调用,静态方法由类调用,因为调用方法是通过isa和super指针实现的。因此对象只能调用类对象的方法,而类对像能调用元类的方法。
isa.png

这张图描述如下:

  • 类的实例对象的 isa 指向它的类;类的 isa 指向该类的 metaclass;
  • 类的 super_class 指向其父类,如果该类为根类则值为 NULL;
  • metaclass 的 isa 指向根 metaclass,如果该 metaclass 是根 metaclass则指向自身;
  • metaclass 的 super_class 指向父 metaclass,如果该 metaclass 是根 metaclass则指向该 metaclass 对应的类;
  • Object-C 为每个类的定义生成两个 objc_class ,一个普通的 class,另一个即metaclass。我们可以在运行期创建这两个 objc_class 数据结构,然后使用 objc_addClass将 class注册到运行时系统中,以此实现动态地创建一个新的类。

为什么这里说生成两个 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;

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

  • isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。
  • super_class:父类,如果该类已经是最顶层的根类,那么它为NULL。
  • version:类的版本信息,默认为0
  • info:供运行期使用的一些位标识。
  • instance_size:该类的实例变量大小
  • ivars:成员变量的数组

下面来个例子:

  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

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

推荐阅读更多精彩内容