文档章节

Runtime<1>

iShown
 iShown
发布于 2015/11/04 20:16
字数 2113
阅读 2
收藏 0

Runtime数据结构
在Objective-C中,使用[receiver message]语法并不会马上执行receiver对象的message方法的代码,而是向receiver发送一条message消息,这条消息可能由receiver来处理,也可能由转发给其他对象来处理,也有可能假装没有接收到这条消息而没有处理。其实[receiver message]被编译器转化为:

id objc_msgSend ( id self, SEL op, ... );
下面从两个数据结构id和SEL来逐步分析和理解Runtime有哪些重要的数据结构。

SEL
SEL是函数objc_msgSend第二个参数的数据类型,表示方法选择器,按下面路径打开objc.h文件


SEL Data Structure
查看到SEL数据结构如下:

typedef struct objc_selector *SEL;
其实它就是映射到方法的C字符串,你可以通过Objc编译器命令@selector()或者Runtime系统的sel_registerName函数来获取一个SEL类型的方法选择器。
如果你知道selector对应的方法名是什么,可以通过NSString* NSStringFromSelector(SEL aSelector)方法将SEL转化为字符串,再用NSLog打印。

id
接下来看objc_msgSend第一个参数的数据类型id,id是通用类型指针,能够表示任何对象。按下面路径打开objc.h文件


id Data Structure.png
查看到id数据结构如下:

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类。

注意:根据Apple的官方文档Key-Value Observing Implementation Details提及,key-value observing是使用isa-swizzling的技术实现的,isa指针在运行时被修改,指向一个中间类而不是真正的类。所以,你不应该使用isa指针来确定类的关系,而是使用class方法来确定实例对象的类。
Class
isa指针的数据类型是Class,Class表示对象所属的类,按下面路径打开objc.h文件


Class Data Structure
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
可以查看到Class其实就是一个objc_class结构体指针,但这个头文件找不到它的定义,需要在runtime.h才能找到objc_class结构体的定义。

按下面路径打开runtime.h文件


objc_class Data Structure
查看到objc_class结构体定义如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
注意:OBJC2_UNAVAILABLE是一个Apple对Objc系统运行版本进行约束的宏定义,主要为了兼容非Objective-C 2.0的遗留版本,但我们仍能从中获取一些有用信息。
让我们分析一些重要的成员变量表示什么意思和对应使用哪些数据结构。

isa表示一个Class对象的Class,也就是Meta Class。在面向对象设计中,一切都是对象,Class在设计中本身也是一个对象。我们会在objc-runtime-new.h文件找到证据,发现objc_class有以下定义:

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

  ......
}
由此可见,结构体objc_class也是继承objc_object,说明Class在设计中本身也是一个对象。

其实Meta Class也是一个Class,那么它也跟其他Class一样有自己的isa和super_class指针,关系如下:


Class isa and superclass relationship from Google

上图实线是super_class指针,虚线是isa指针。有几个关键点需要解释以下:

Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
每个Class都有一个isa指针指向唯一的Meta class
Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。
每个Meta class的isa指针都指向Root class (meta)。
super_class表示实例对象对应的父类
name表示类名
ivars表示多个成员变量,它指向objc_ivar_list结构体。在runtime.h可以看到它的定义:

struct objc_ivar_list {
  int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
  int space                                                OBJC2_UNAVAILABLE;
#endif
  /* variable length structure */
  struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}
objc_ivar_list其实就是一个链表,存储多个objc_ivar,而objc_ivar结构体存储类的单个成员变量信息。

methodLists表示方法列表,它指向objc_method_list结构体的二级指针,可以动态修改*methodLists的值来添加成员方法,也是Category实现原理,同样也解释Category不能添加实例变量的原因。在runtime.h可以看到它的定义:

struct objc_method_list {
  struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

  int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
  int space                                                OBJC2_UNAVAILABLE;
#endif
  /* variable length structure */
  struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}
同理,objc_method_list也是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息。

cache用来缓存经常访问的方法,它指向objc_cache结构体,后面会重点讲到。

protocols表示类遵循哪些协议
Method
Method表示类中的某个方法,在runtime.h文件中找到它的定义:

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}
其实Method就是一个指向objc_method结构体指针,它存储了方法名(method_name)、方法类型(method_types)和方法实现(method_imp)等信息。而method_imp的数据类型是IMP,它是一个函数指针,后面会重点提及。

Ivar
Ivar表示类中的实例变量,在runtime.h文件中找到它的定义:

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}
Ivar其实就是一个指向objc_ivar结构体指针,它包含了变量名(ivar_name)、变量类型(ivar_type)等信息。

IMP
在上面讲Method时就说过,IMP本质上就是一个函数指针,指向方法的实现,在objc.h找到它的定义:

/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
当你向某个对象发送一条信息,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。

Cache
顾名思义,Cache主要用来缓存,那它缓存什么呢?我们先在runtime.h文件看看它的定义:

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};
Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

消息发送
前面从objc_msgSend作为入口,逐步深入分析Runtime的数据结构,了解每个数据结构的作用和它们之间关系后,我们正式转入消息发送这个正题。

objc_msgSend函数
在前面已经提过,当某个对象使用语法[receiver message]来调用某个方法时,其实[receiver message]被编译器转化为:

id objc_msgSend ( id self, SEL op, ... );
现在让我们看一下objc_msgSend它具体是如何发送消息:

首先根据receiver对象的isa指针获取它对应的class
优先在class的cache查找message方法,如果找不到,再到methodLists查找
如果没有在class找到,再到super_class查找
一旦找到message这个方法,就执行它实现的IMP。



© 著作权归作者所有

上一篇: Runtime<2>
下一篇: Objective-C的Runtime
iShown
粉丝 17
博文 67
码字总数 46936
作品 0
浦东
高级程序员
私信 提问
始终显示公司名不存在

@老法海 你好,想跟你请教个问题: 我根据模拟请求,返回的json结果如下 { "__DEBUG__": { "INFO": [ "[ app_init ] --START--", "Run Behavior\BuildLiteBehavior [ RunTime:0.000008s ]",......

gread_369
2016/08/25
451
2
msgpack 真的比 protobuf buffers 快 4 倍吗?

hprose serialize Runtime:0.2334s json encode Runtime:15.2883s serialize Runtime:0.7090s msgpack_pack Runtime:0.6883s hprose unserialize Runtime:0.5439s json decode Runtime:24.29......

andot
2015/04/18
4K
4
runtime 运行时机制 完全解读

我们前面已经讲过一篇runtime 原理,现在这篇文章主要介绍的是runtime是什么以及怎么用!希望对读者有所帮助! 首先,第一个问题,1》runtime实现的机制是什么,怎么用,一般用于干嘛?这个问...

哥特复心
2014/08/06
82.3K
10
服务器性能测试

#!/bin/bash logfile=/tmp/streelog.log echo "stress -c 4 -i 4 --verbose --timeout 1m" >> logfile stress -c 4 -i 4 --verbose --timeout 1m & >> $logfile sleep 30 top -b -n 1 | head......

emma_cql
2017/08/09
0
0
kata container社区进展

3月18日 1、当前社区的主要任务是向kata倒入runtime。按照社区说法此阶段可以使用intel cc 或runv去尝试kata的各种好处 2、社区同时尝试将kata接入各种工程。在3月18日这周,kata提供了接入z...

marshalzxy
2018/04/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

JS其他类型值转化为Boolean类型规则

本文转载于:专业的前端网站➤JS其他类型值转化为Boolean类型规则 由于最近在笔试的时候,发现好多关于其他类型转化为Boolean类型的题目,因此总结一下! 一、String类型转化为Boolean 1.转化...

前端老手
17分钟前
4
0
EurekaClient自动装配及启动流程解析

在上篇文章中,我们简单介绍了EurekaServer自动装配及启动流程解析,本篇文章则继续研究EurekaClient的相关代码 老规矩,先看spring.factories文件,其中引入了一个配置类EurekaDiscoveryClie...

Java学习录
23分钟前
5
0
析构函数是否必须为虚函数?为何?

在C++中,基类指针可以指向一个派生类的对象。如果基类的析构函数不是虚函数,当需要delete这个指向派生类的基类指针时,就只会调用基类的析构函数,而派生类的析构函数无法被调用。容易造成...

天王盖地虎626
24分钟前
4
0
【TencentOS tiny】深度源码分析(7)——事件

引言 大家在裸机编程中很可能经常用到flag这种变量,用来标志一下某个事件的发生,然后在循环中判断这些标志是否发生,如果是等待多个事件的话,还可能会if((xxx_flag)&&(xxx_flag))这样子做...

杰杰1号
27分钟前
5
0
聊聊nacos client的ServerHttpAgent

序 本文主要研究一下nacos client的ServerHttpAgent HttpAgent nacos-1.1.3/client/src/main/java/com/alibaba/nacos/client/config/http/HttpAgent.java public interface HttpAgent { ......

go4it
33分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部