iOS中的runtime和OC对象的底层结构

首先我们来创建一个类,然后再创建这个类的一个对象
创建一个父类Person

@interface Person : NSObject 
{
int _age;
int _no;
int _height;
}

-(void)personInstanceMethod;
+(void)personClassMethod;

@end

@implementation Person

- (void)personInstanceMethod{
    
}

+ (void)personClassMethod{
    
}
@end

创建Person的子类Student,为什么要这样创建,方便后面讲清楚对象与类,类与父类,类与元类之间的关系

@interface Student : Person
{
    int _sex;
}

-(void)studentInstanceMethod;
+(void)studentClassMethod;

@end

@implementation Student

- (void)studentInstanceMethod{
    
}

+ (void)studentClassMethod{
    
}
@end

然后我们创建一个Student的对象

Student *student = [[Student alloc]init];
[student personInstanceMethod];

在OC中每个对象都是一个结构体,结构体中都包含一个isa的成员变量,其位于成员变量的第一位。isa的成员变量之前都是Class类型的,后来苹果将其改为isa_t

struct objc_object {
private:
    isa_t isa;
};

上面是对象的结构体,你们发现里面只有一个isa指针,下面我们来看看类的结构体
查看网上相关资料,我们会发现出现了两种结构体
第一种

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;
/* Use `Class` instead of `struct objc_class *` */

但是看里面的相关代码会发现有个前置条件#if !__OBJC2__,而我们现在基本上用的都是objective-c 2.0,所以上面的结构体只能反映以前的结构
下面是新的结构体

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_class是继承于objc_object的,因此objc_class中也包含isa_t类型的isaobjc_class的定义可以理解成下面这样:

struct objc_class {
    isa_t isa;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

对象与类,类与父类,类与元类之间的关系

了解完内部结构后,我们就可以进一步来阐述对象与类,类与父类,类与元类之间的关系,其中元类和类采用的是同一种数据结构
其中isa必须指向一个地方,而superclass当没有父类的时候可以为nil

关系指向图

对象里面的isa指向自己的类,类里面的superclass指向自己的父类,isa指向元类,通过上面这张图,再来理解下面这张随处可见的图,就容易理解多了,
所以类的关系图

到此,我们对象与类,类与父类,类与元类的关系就说清楚了

类中的实例方法、类方法和属性的结构

objc_class中,我们主要研究class_rw_t,rw_tread_write_table的缩写,意思是可读可写的表。bits.data()返回class_rw_tclass_rw_t里面存放的什么信息呢?我们点击进入class_rw_t看看:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;//方法信息
    property_array_t properties;//属性信息
    protocol_array_t protocols;//协议信息
}

可以看到我们想看的方法,属性,协议信息.只是还没看到成员变量信息. class_rw_t中有一个class_ro_t,ro_treadOnly_table的缩写,意思是只读的表.我们点击进入const class_ro_t看看里面存放哪些信息:

struct class_ro_t {
    const char * name;//类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;//成员变量
}

下面一张图,能一目了然展示Class的内存结构:

Class的内存结构

元类的方法列表中主要是类方法的信息

结合上面的结构,再讲讲面试中经常被问到的东西

SELSEL(选择器)是方法的selector的指针。方法的selector表示运行时方法的名字。OC在编译时,会依据每一个方法的名字、参数,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL

IMPIMP是一个函数指针,指向方法最终实现的首地址。SEL就是为了查找方法的最终实现IMP

Method:用于表示类定义中的方法,它的结构体中包含一个SELIMP,相当于在SELIMP之间作了一个映射。

消息机制:任何方法的调用本质就是发送一个消息。编译器会将消息表达式[receiver message]转化为一个消息函数objc_msgSend(receiver, selector)
objc_msgSend做了如下事情:

  1. 通过对象的isa指针获取类的结构体。
  2. 在结构体的方法表里查找方法的selector。
  3. 如果没有找到selector,则通过objc_msgSend结构体中指向父类的指针找到父类,并在父类的方法表里查找方法的selector。
  4. 依次会一直找到NSObject。
  5. 一旦找到selector,就会获取到方法实现IMP。
  6. 传入相应的参数来执行方法的具体实现。
  7. 如果最终没有定位到selector,就会走消息转发流程

Runtime的使用:获取属性列表,获取成员变量列表,获得方法列表,获取协议列表,方法交换(黑魔法),动态的添加方法,调用私有方法,为分类添加属性。

1.什么是isa指针?

isa指针的作用:当我们向一个对象发送消息时,runtime会根据这个对象的isa指针找到这个对象所属的类,在这个类的方法列表及父类的方法列表中,寻找与消息对应的selector指向的方法,找到后就运行这个方法。

2.是如何找到IMP的?
在寻找IMP的地址时,runtime提供了两种方法

IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)

而根据官方描述,第一种方法可能会更快一些
对于第一种方法而言,类方法和实例方法实际上都是通过调用class_getMethodImplementation()来寻找IMP地址的,不同之处在于传入的第一个参数不同类方法(假设有一个类A)

class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));
类方法当中传入的是元类

实例方法

class_getMethodImplementation([A class],@selector(methodName));

而对于第二种方法而言,传入的参数只有Method,区分类方法和实例方法在于封装Method的函数
类方法

Method class_getClassMethod(Class cls, SEL name)

实例方法

Method class_getInstanceMethod(Class cls, SEL name)

最后调用

IMP method_getImplementation(Method m)

获取IMP地址

3.消息转发机制
当最后我们要调用的方法找不到时,通常我们会报一个“没有此方法的错误”,在报错之前,我们实际上会有三次解决该错误造成闪退的机会

当对象无法接收消息,就会启动消息转发机制,通过这一机制,告诉对象如何处理未知的消息。

①动态方法解析

对象接收到未知的消息时,首先会调用所属类的方法

(实例方法)
+resolveInstanceMethod:

或 者

(类方法)
+resolveClassMethod:

在这个方法中,我们有机会为该未知消息新增一个”处理方法”。使用该“处理方法”的前提是已经实现,只需要在运行时通过class_addMethod函数,动态的添加到类里面就可以了。

②备用接收者

如果在上一步无法处理消息,则Runtime会继续调下面的方法。

- (id)forwardingTargetForSelector:(SEL)aSelector

如果这个方法返回一个对象,则这个对象会作为消息的新接收者。
注意这个对象不能是self自身,否则就是出现无限循环。如果没有指定对象来处理aSelector,则应该

return [super forwardingTargetForSelector:aSelector]。

但是我们只将消息转发到另一个能处理该消息的对象上,无法对消息进行处理,例如操作消息的参数和返回值。

③完整消息转发

如果在上一步还是不能处理未知消息,则唯一能做的就是启用完整的消息转发机制。此时会调用以下方法:

- (id)forwardInvocation:(NSInvocation *)anInvocation

这是最后一次机会将消息转发给其它对象。创建一个表示消息的NSInvocation对象,把与消息的有关全部细节封装在anInvocation中,包括selector,目标(target)和参数。在forwardInvocation方法中将消息转发给其它对象。

forwardInvocation:方法的实现有两个任务:

a. 定位可以响应封装在anInvocation中的消息的对象。

b. 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,runtime会提取这一结果并发送到消息的原始发送者。

在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改。另外,若发现消息不应由本类处理,则应调用父类的同名方法,以便继承体系中的每个类都有机会处理。

另外,必须重写下面的方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息转发机制从这个方法中获取信息来创建NSInvocation对象。

NSObjectforwardInvocation方法只是调用了doesNotRecognizeSelector方法,它不会转发任何消息。如果不在以上所述的三个步骤中处理未知消息,则会引发异常。这里为什么提到NSObject,因为NSObject是最后的父类,一直找不到就会找到NSObject中。

forwardInvocation就像一个未知消息的分发中心,将这些未知的消息转发给其它对象。或者也可以像一个运输站一样将所有未知消息都发送给同一个接收对象,取决于具体的实现。

消息的转发机制可以用下图来帮助理解。


转发机制

4.isa结构体内容
isa_t定义
isa_t是一个union的结构对象,union类似于C++结构体,其内部可以定义成员变量和函数。

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

    Class cls;
    uintptr_t bits;

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1; // 是32位还是64位
        uintptr_t has_assoc         : 1; // 对象是否含有或曾经含有关联引用,如果没有关联引用,可以更快的释放对象
        uintptr_t has_cxx_dtor      : 1; // 表示是否有C++析构函数或OC的析构函数
        uintptr_t shiftcls          : 33; // 对象指向类的内存地址,也就是isa指向的地址
        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__
// ····
# else
// ····
# endif
};

具体例子参考
runtime运行时 isa指针 SEL方法选择器 IMP函数指针 Method方法 runtime消息机制 runtime的使用
内容有部分参考:
OC对象的底层结构及isa、superClass详解
探秘Runtime - 剖析Runtime结构体

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