第八更 runtime总结

一、什么是runtime
C++编写的程序通过编译器直接把函数地址硬编码进入可执行文件;而Objective-C无法通过编译器直接把函数地址硬编码进入可执行文件,而是在程序运行的时候,利用Runtime根据条件判断作出决定。函数标识与函数过程的真正内容之间的关联可以动态修改。Runtime是Objective不可缺少的重要一部分。

二、Objective-C的元素认知

2.1打开/Public Headers/objc.h文件可以看到如下定义:

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// 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;
#endif

Class是一个指向objc_class结构体的指针,而id是一个指向objc_object结构体的指针,其中的isa是一个指向objc_class结构体的指针。其中的id就是我们所说的对象,Class就是我们所说的类。

打开/Public Headers/runtime.h文件
objc_class的定义如下:

typedef struct objc_class *Class;
struct objc_class { 
 Class isa                                 OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
 Class super_class                         OBJC2_UNAVAILABLE; // 父类
 const char *name                          OBJC2_UNAVAILABLE; // 类名
 long version                              OBJC2_UNAVAILABLE; // 类的版本信息,默认为0,可以通过runtime函数class_setVersion或者class_getVersion进行修改、读取
 long info                                 OBJC2_UNAVAILABLE; // 类信息,供运行时期使用的一些位标识,如CLS_CLASS (0x1L) 表示该类为普通 class,其中包含实例方法和变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
 long instance_size                        OBJC2_UNAVAILABLE; // 该类的实例变量大小(包括从父类继承下来的实例变量)
 struct objc_ivar_list *ivars              OBJC2_UNAVAILABLE; // 该类的成员变量地址列表
 struct objc_method_list **methodLists     OBJC2_UNAVAILABLE; // 方法地址列表,与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储实例方法,如CLS_META (0x2L),则存储类方法;
 struct objc_cache *cache                  OBJC2_UNAVAILABLE; // 缓存最近使用的方法地址,用于提升效率;
 struct objc_protocol_list *protocols      OBJC2_UNAVAILABLE; // 存储该类声明遵守的协议的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */

由以上代码可见,类与对象的区别就是类比对象多了很多特征成员,类也可以当做一个objc_object来对待,也就是说类和对象都是对象,分别称作类对象(class object)和实例对象(instance object),这样我们就可以区别对象和类了。

isa:objc_object(实例对象)中isa指针指向的类结构称为class(也就是该对象所属的类)其中存放着普通成员变量与动态方法(“-”开头的方法);此处isa指针指向的类结构称为metaclass,其中存放着static类型的成员变量与static类型的方法(“+”开头的方法)。

super_class: 指向该类的父类的指针,如果该类是根类(如NSObject或NSProxy),那么super_class就为nil。

2.2 SEL
SEL是selector在Objective-C中的表示类型。selector可以理解为区别方法的ID。

typedef struct objc_selector *SEL;

objc_selector的定义如下:

struct objc_selector {
    char *name;                       OBJC2_UNAVAILABLE;// 名称
    char *types;                      OBJC2_UNAVAILABLE;// 类型
};

name和types都是char类型。

2.3 IMP

终于到IMP了,它在objc.h中得定义如下:

typedef id (*IMP)(id, SEL, ...);

IMP是“implementation”的缩写,它是由编译器生成的一个函数指针。当你发起一个消息后(下文介绍),这个函数指针决定了最终执行哪段代码

2.4 METHOD

Method代表类中的某个方法的类型。

typedef struct objc_method *Method;

objc_method的定义如下:

struct objc_method {
    SEL method_name                   OBJC2_UNAVAILABLE; // 方法名
    char *method_types                OBJC2_UNAVAILABLE; // 方法类型
    IMP method_imp                    OBJC2_UNAVAILABLE; // 方法实现
}

方法名method_name类型为SEL,上文提到过。
方法类型method_types是一个char指针,存储着方法的参数类型和返回值类型。
方法实现method_imp的类型为IMP,上文提到过。

2.5 Ivar

Ivar代表类中实例变量的类型

typedef struct objc_ivar *Ivar;

objc_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
}

2.6 objc_property_t
objc_property_t是属性,它的定义如下:

typedef struct objc_property *objc_property_t;

objc_property是内置的类型,与之关联的还有一个objc_property_attribute_t,它是属性的attribute,也就是其实是对属性的详细描述,包括属性名称、属性编码类型、原子类型/非原子类型等。它的定义如下:

typedef struct {
    const char *name; // 名称
    const char *value;  // 值(通常是空的)
} objc_property_attribute_t;

2.7 Cache
Catch的定义如下:

typedef struct objc_cache *Cache

objc_cache的定义如下:

struct objc_cache {
    unsigned int mask                   OBJC2_UNAVAILABLE;
    unsigned int occupied               OBJC2_UNAVAILABLE;
    Method buckets[1]                   OBJC2_UNAVAILABLE;
};

mask: 指定分配cache buckets的总数。在方法查找中,Runtime使用这个字段确定数组的索引位置。
occupied: 实际占用cache buckets的总数。
buckets: 指定Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。
objc_msgSend(下文讲解)每调用一次方法后,就会把该方法缓存到cache列表中,下次的时候,就直接优先从cache列表中寻找,如果cache没有,才从methodLists中查找方法。

2.8 Catagory

这个就是我们平时所说的类别了,很熟悉吧。它可以动态的为已存在的类添加新的方法。
它的定义如下:

typedef struct objc_category *Category;

objc_category的定义如下:

struct objc_category {
    char *category_name                           OBJC2_UNAVAILABLE; // 类别名称
    char *class_name                              OBJC2_UNAVAILABLE; // 类名
    struct objc_method_list *instance_methods     OBJC2_UNAVAILABLE; // 实例方法列表
    struct objc_method_list *class_methods        OBJC2_UNAVAILABLE; // 类方法列表
    struct objc_protocol_list *protocols          OBJC2_UNAVAILABLE; // 协议列表
}

因为是入门,以上就列举这些吧!

三、Objective-C的消息传递

3.1 基本消息传递

在面向对象编程中,对象调用方法叫做发送消息。在编译时,程序的源代码就会从对象发送消息转换成Runtime的objc_msgSend函数调用。
例如某实例变量receiver实现某一个方法oneMethod

[receiver oneMethod];

Runtime会将其转成类似这样的代码

objc_msgSend(receiver, selector);

具体会转换成什么代码呢?
Runtime会根据类型自动转换成下列某一个函数:
objc_msgSend:普通的消息都会通过该函数发送
objc_msgSend_stret:消息中有数据结构作为返回值(不是简单值)时,通过此函数发送和接收返回值
objc_msgSendSuper:和objc_msgSend类似,这里把消息发送给父类的实例
objc_msgSendSuper_stret:和objc_msgSend_stret类似,这里把消息发送给父类的实例并接收返回值
当消息被发送到实例对象时,是如图所示处理的(图片源自网络):

objective-runtime-2.png

objc_msgSend函数的调用过程:

  • 第一步:检测这个selector是不是要忽略的。
  • 第二步:检测这个target是不是nil对象。nil对象发送任何一个消息都会被忽略掉。
  • 第三步:
    1.调用实例方法时,它会首先在自身isa指针指向的类(class)methodLists中查找该方法,如果找不到则会通过class的super_class指针找到父类的类对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根class;
    2.当我们调用某个某个类方法时,它会首先通过自己的isa指针找到metaclass,并从其中methodLists中查找该类方法,如果找不到则会通过metaclass的super_class指针找到父类的metaclass对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根metaclass;
  • 第四部:前三部都找不到就会进入动态方法解析(看下文)。**

四、动态解析
动态解析流程图(图片来自网络):


objective-runtime-6.png

第一步:通过resolveInstanceMethod:或者resolveClassMethod方法决定是否动态添加方法。如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回No,则进入下一步;
第二步:这步会进入forwardingTargetForSelector:方法,用于指定备选对象响应这个selector,不能指定为self。如果返回某个对象则会调用对象的方法,结束。如果返回nil,则进入第三部;
第三部:这步我们要通过methodSignatureForSelector:方法签名,如果返回nil,则消息无法处理。如果返回methodSignature,则进入下一步;
第四部:这步调用forwardInvocation:方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector方法,若我们没有实现这个方法,那么就会crash。

demo传送门:https://www.ianisme.com/ios/2019.html

五、runtime项目应用

5.1、实例变量遍例

遍例类的所有实例变量,实现自动归档-反归档
Ivar *ivars = class_copyIvarList([self class], &count);

//    KVC中setValue中使用
//    我们知道在KVC中如果直接setValue如果对象没有这个属性或者是变量就会直接Crash,如:
    SomeObj *obj = [[SomeObj alloc]init];
    [obj setValue:@"value" forKey:@"objName"];
//    SomeObj 没有objName这个属性
  //    这段代码会直接Crash,使用runtime遍例实例变量避免

5.2、动态关联对象

使用Runtime来在一个已有对象上动态的挂载另一个对象
如:如果你在对象传递(传参)的时候需要用到某个属性,按照以往的思路:我继承这个类重新创建一个新类就完事了,这个思路没有问题,但麻烦,要是有一个方法能直接将我想要的属性挂载上去岂不是更好?代码简单、易懂。
实际开发中,一个UIViewController中多个UITableView(或UIAlertView)它们的代理相同,动态关联后在代理方法中就可以分别不同对象进行不同处理。
下面就来讲解下如何使用Runtime来 在已有对象上动态挂载另外一个对象。

5.3、动态关联函数/方法
动态关联方法,在运时期动态增加、交换方法。
替换系统方法,自动实现功能。
遍例系统类的方法,查看新特性。

demo实例:https://www.jianshu.com/p/902749ed3e4c

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

推荐阅读更多精彩内容