Runtime梳理(一)

挖就挖底层.png

看这篇文章关于Runtime的话,主要是有OC基础的同学,共勉吧!
本文我自己的目的是:“ 弄清楚什么是 Runtime,并对Runtime有一定的基本了解,可以在开发过程中把Runtime运用自如.”

什么是 Runtime

  • 简而言之,Objective-C Runtime是一个将C语言转化为面向对象语言的扩展.
  • C++是基于静态类型,Objective-C是基于动态运行时类型.
  • 通过Runtime把程序转为可令机器读懂的机器语言,Runtime是Objective不可缺少的重要一部分.

本章分成两个部分进行学习:1. OC元素的认知 2. OC消息传递

第一部分:OC元素的认知:

子类 类 父类 元类 根类 其中指针指向和集成的关系自己梳理,这里不多说,下面我们讲Runtime的构成,最快捷的了解方法就是进去ios 9.2/usr/include/objc/objc或runtime随便看看。总结如下:

主要有七个构件:1.id和Class 2. SEL 3. Method 4. IMP 5. Ivar 6.Cache 7. Category

一. id和Class

objc_isa_availability
typedef struct
object_class *Class、、类
objc_object Class object_class isa OBJC_ISA_AVAILABILITY、、isa指针
objc_object *id

  1. Class是一个指向objc_class结构体的指针,
  2. id是一个指向objc_object结构体的指针,
  3. isa是一个指向objc_class`结构体的指针。
  4. id就是我们所说的对象,Class就是我们所说的类。

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、、该类的实例变量大小(包括从父类继承下来的实例变量)

objec_ivar_list *ivars OBJC2_UNAVAILABLE、、该类的成员变量地址列表

objc_method_list **methodLists OBJC2_UNAVAILABLE、、方法地址列表,与info的一些标志位有关,如CLS_CLASS (0x1L),则存储实例方法,如CLS_META (0x2L),则存储类方法;

objc_cache_list *cache OBJC2_UNAVAILABLE、、缓存最近使用的方法地址,用于提升效率;

objc_protocol_list *protocols OBJC2_UNAVAILABLE、、存储该类声明遵守的协议的列表

二. SEL

  1. SELselector在Objective-C中的表示类型。
  1. selector可以理解为区别方法的ID。
    objc_selector *SEL
    objc_selector char * name OBJC2_UNAVLABLE名称
    char *type

nametypes都是char类型。

三. IMP

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

四. Method

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

objc_method *Method
objc_method SEL method_name OBJC2_UNAVAILABLE、、方法名
obcj_method char *method_types OBJC2_UNAVAILABLE、、方法类型
IMP method_imp OBJC2_UNAVAILABLE、、方法实现

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

五. Ivar

Ivar代表类中实例变量的类型
objc_ivar *Ivar
objc_ivar char *ivar_name OBJC2_UNAVAILABLE、、变量名
objc_ivar char *ivar_type OBJC2_UNAVAILABLE、、变量类型
objc_ivar int ivar_offset OBJC2_UNAVAILABLE、、基地址偏移字节
objc_ivar int space OBJC2_UNAVAILABLE、、占用空间
objc_propery_t
objc_property_t是属性
objc_property *objc_property_t;

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

const char*name、、名称
const char*value、、值(通常为空的)
------ objc_property_attribute_t

六. Cache

objc_cache *Cache
objc_cache unsigned int mask OBJC2_UNAVAILABLE、、
unsigned int occupiedOBJC2_UNAVAILABLE、、
Method buckets[1]OBJC2_UNAVAILABLE、、

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

七. Category

就是我们平时所说的类别了,很熟悉吧。它可以动态的为已存在的类添加新的方法
objc_catagory *Category
char *category OBJC2_UNAVAILABLE、、类别名称
char *class_name OBJC2_UNAVAILABLE、、类名
objc_method_list *instance_methods OBJC2_UNAVAILABLE、、实例方法列表
objc_method_list *class_methods OBJC2_UNAVAILABLE、、类方法列表
objc_method_list *protocols OBJC2_UNAVAILABLE、、协议列表

这小补充一点:objc_AssociationPolicy关联策略,有以下几种策略:

enum { 
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 
OBJC_ASSOCIATION_RETAIN = 01401, 
OBJC_ASSOCIATION_COPY = 01403 
};

// 后面用得到。。。。

第二部分:OC消息传递:

一. 基本消息传递

1.在面向对象编程中,对象调用方法叫做发送消息。
2.在编译时,程序的源代码就会从对象发送消息转换成Runtime的objc_msgSend函数调用。

例如某实例变量receiver实现某一个方法oneMethod
[receiver oneMethod] Runtime会将其转成类似这样的代码:objc_msgSend(receiver, selector);

  1. Runtime会根据类型自动转换成下列某一个函数:
  1. objc_msgSend:普通的消息都会通过该函数发
  2. objc_msgSend_stret:消息中有数据结构作为返回值(不是简单值)时,通过此函数发送和接收返回值
  3. objc_msgSendSuper:objc_msgSend类似,这里把消息发送给父类的实例
    5.objc_msgSendSuper_stret:objc_msgSend_stret类似,这里把消息发送给父类的实例并接收返回值

二. objc_msgSend函数的调用过程:

  • 第一步:检测这个selector是不是要忽略的。
    第二步:检测这个target是不是nil对象。nil对象发送任何一个消息都会被忽略掉。
    第三步:

    1. 调用实例方法时,它会首先在自身isa指针指向的类(class)methodLists中查找该方法,
      如果找不到则会通过class的super_class指针找到父类的类对象结构体,
      然后从methodLists中查找该方法,
      如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,
      直至根class
    2. 当我们调用某个某个类方法时,它会首先通过自己的isa指针找到metaclass
      并从其中methodLists中查找该类方法,
      如果找不到则会通过metaclasssuper_class指针找到父类的metaclass对象结构体,
      然后从methodLists中查找该方法,
      如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,
      直至根metaclass

第四步:前三部都找不到就会进入动态方法解析(看下文

三. 消息动态解析

演示:

  • 第一步:
    通过resolveInstanceMethod:方法决定是否动态添加方法。
    如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;
    如果返回No,则进入下一步;

  • 第二步:
    这步会进入forwardingTargetForSelector:方法,
    用于指定备选对象响应这个selector,不能指定为self
    如果返回某个对象则会调用对象的方法,结束。
    如果返回nil,则进入第三部;

  • 第三步:
    这步我们要通过methodSignatureForSelector:方法签名,
    如果返回nil,则消息无法处理。
    如果返回methodSignature,则进入下一步;

  • 第四步:
    这步调用forwardInvocation:方法,
    我们可以通过anInvocation对象做很多处理,
    比如修改实现方法,修改响应对象等,如果方法调用成功,则结束。
    如果失败,则进入doesNotRecognizeSelector方法,
    若我们没有实现这个方法,那么就会crash

案例:

import <Foundation/Foundation.h>

if TARGET_IPHONE_SIMULATOR

import <objc/objc-runtime.h>

else

import <objc/runtime.h>

import <objc/message.h>

endif


 *  自定义函数
> ````
void sayFunction(id self, SEL _cmd, id some)
{
    NSLog(@"%@岁的%@说:%@",
          object_getIvar(self, class_getInstanceVariable([self class], "_age")),[self valueForKey:@"name"], some);
}

int main(int argc, const char * argv[]) {
// 添加了一个类
Class People = objc_allocateClassPair([NSObject class], "Person", 0);
// 给这个类添加了两属性 name age
class_addIvar(People, "_name", sizeof(NSString*), log2(sizeof(NSString *)), @encode(NSString ));
class_addIvar(People, "_age", sizeof(int), sizeof(int), @encode(int));
// 生成一个方法
SEL s = sel_registerName("say:");
class_addMethod(People, s, (IMP)sayFunction, "good");
// 注册这个类
objc_registerClassPair(People);
// 对象
id peopleInstance = [[People alloc] init];
// 属性值
[peopleInstance setValue:@"小萌" forKey:@"name"];
Ivar ageIvar = class_getInstanceVariable(People, "_age");
// 动态赋值
object_setIvar(peopleInstance, ageIvar, @18);
// 调用方法
((void (
)(id, SEL, id))objc_msgSend)(peopleInstance, s, @"大家好");
// 销毁对象
peopleInstance = nil;
objc_disposeClassPair(People);
return 0;
}


打印结果如下:
> 2016-04-08 18:52:15.009 RuntimeLine[4710:394843] 18岁的小明说:大家好 
Program ended with exit code: 0

默认会出现以下错误:
“`objc_msgSend()`报错Too many arguments to function call ,expected 0,have3”
直接通过`objc_msgSend(self, setter, value)`是报错,说参数过多。
请这样解决:
* Build Setting–> Apple LLVM 7.0 – Preprocessing–> Enable Strict Checking of objc_msgSend Calls 改为 NO
也可以这样写(推荐):
`((void(*)(id,SEL,id))objc_msgSend)(peopleInstance,s,@"大家好");`
强制转换`objc_msgSend`函数类型为带三个参数且返回值为void函数,然后才能传三个参数。

> ###案例小结:
看看我们都干了什么?!What have we done?!!!
此实战内容是,动态创建一个类,并创建成员变量和方法,最后赋值成员变量并发送消息。其中成员变量的赋值使用了KVC和object_setIvar函数两种方式,这些东西大家举一反三就可以了

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,670评论 0 9
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 721评论 0 2
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 737评论 0 1
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,161评论 0 7
  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 595评论 0 2