iOS --- Runtime

在开始Runtime前,先看一个例子。
新建一个继承于NSObject的Dog类,.h文件添加一个run方法,run方法在.m文件不实现。


doghfile.png

ViewController中创建Dog类实例。


image.png

各位看官猜猜会发生什么事?
没错,unrecognized selector sent to instance 0x600003e17170, 这在开发中是很常见的。找不到这个方法。
image.png

再做一个变更,假如Dog继承自Animal类,Animal的.m文件实现run方法,那会怎样?


image.png

run方法在控制台上打印出来了,这能证明在运行时,如果找不到方法,会向父类查找吗?
我们都知道,NSObject是所有类的父类,如果run方法能在NSObject这个类实现,那么就能证明实例对象在找方法时,会先从本类查找,如果本类没有,就向父类查找,如果仍没有,则报错。


image.png

先创建一个NSObect分类。此处多加一个func(不知此方法用途的,请自行百度哦)

image.png

控制台打印的是:
image.png

至此,可以大胆确定:

实例对象在找方法时,会先从本类查找,如果本类没有,就向父类查找,如果仍没有,则报错。

理论已经了解,那么代码实现如何?


image.png

Runtime的主要特性是什么呢? ---- 消息(方法)传递
如果消息(方法)在对象中找不到,就进行转发。

这里依照以下3个方面进行。
1.Runtime消息传递
2.Runtime消息转发
3.Runtime应用

Runtime消息传递:
以此代码为例:


image.png

[doLin run],转化过来就是objc_msgSend(doLin, run)

这有4个步骤:

  1. 通过obj的isa指针找到它的 class(此例为Dog) ;
  2. 在 class 的 method list 找 run ;
  3. 如果 class 中没到 run,继续往它的 superClass 中找 ;
  4. 一旦找到 run 这个函数,就去执行它的实现IMP 。(IMP指针,IMP就是Implementation的缩写,顾名思义,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP)

这确实实现了方法查找,那可能有人要问,如果每次都需要遍历查找,查找效率是否太低下了?

别担心,苹果也帮你想好了。这就要提objc_cache。

objc_class 中另一个重要成员objc_cache 做的事情 - 再找到run 之后,把run 的method_name 作为key ,method_imp作为value 给存起来。当再次收到run 消息的时候,可以直接在cache 里找到,避免去遍历objc_method_list。objc_cache是存在objc_class 结构体中的。


image.png

objec_msgSend的方法定义如下:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)

那消息传递是怎么实现的呢?我们看看对象(object),类(class),方法(method)这几个的结构体:


image.png
  1. 系统首先找到消息的接收对象,然后通过对象的isa找到它的类。
  2. 在它的类中查找method_list,是否有selector方法。
  3. 没有则查找父类的method_list。
  4. 找到对应的method,执行它的IMP。
  5. 转发IMP的return值。

消息传递用到的概念:

类对象(objc_class)
实例(objc_object)
元类(Meta Class)
Method(objc_method)
SEL(objc_selector)
IMP
类缓存(objc_cache)
Category(objc_category)

  1. 类对象(objc_class)

OC类是由Class类型来表示的,实际是指向objc_class结构体的指针。

typedef struct objc_class *Class;

objc/runtime.h,objc_class结构体的定义:


image.png

从上到下包含了:

  1. isa指针
  2. 类名
  3. 版本号
  4. 信息
  5. 实例大小
  6. 实例变量
  7. 方法列表
  8. 缓存
  9. 遵守的协议列表

这个类结构体的第一个成员变量是isa, 也证明了,类是对象,称为类对象。
类对象就是一个结构体struct objc_class,这个结构体存放的数据称为元数据(metadata),

  1. 实例(objc_object)


    image.png

类对象中的元数据存储什么?
答:如何创建一个实例的相关信息。

那么类对象和类方法应该从哪里创建呢?
答:从isa指针指向的结构体创建,类对象的isa指针指向的我们称之为元类(metaclass),元类中保存了创建类对象以及类方法所需的所有信息,因此整个结构应该如下图所示:


image.png
  1. 元类(Meta Class)
    上图整个体系构成了一个自闭环,struct objc_object结构体实例isa指针指向类对象,类对象的isa指针指向了元类,super_class指针指向了父类的类对象,
    而元类的super_class指针指向了父类的元类,那元类的isa指针又指向了自己。
    元类(Meta Class)是一个类对象的类。
    在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。
    为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。
    任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。

  2. Method(objc_method)


    image.png

    Method就是表示能够独立完成一个功能的一段代码,比如:

  • (void)run{
    NSLog(@"This man run so quickly");
    }//这段代码,就是一个函数。

我们来看下objc_method这个结构体的内容:

  1. SEL method_name 方法名
  2. char *method_types 方法类型
  3. IMP method_imp 方法实现

SEL为Key, IMP为Value, 方法查找过程,就是通过相应的SEL查找IMP实现

  1. SEL(objc_selector)
image.png
  1. IMP
    指向最终实现程序的内存地址的指针。


    image.png
  2. 类缓存(objc_cache)
    为了加速消息分发, 系统会对方法和对应的地址进行缓存,就放在上述的objc_cache,所以在实际运行中,大部分常用的方法都是会被缓存起来的,Runtime系统实际上非常快,接近直接执行内存地址的程序速度。

  3. Category(objc_category)

name:是指 class_name 而不是 category_name。(例如,为Dog类添加分类,此name则指Dog)
cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象。
instanceMethods:category中所有给类添加的实例方法的列表。
classMethods:category中所有添加的类方法的列表。
protocols:category实现的所有协议的列表。
instanceProperties:表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的。


image.png

从上面的category_t的结构体中可以看出,分类中可以添加实例方法,类方法,甚至可以实现协议,添加属性,不可以添加成员变量。

消息转发
先看看下面一则图片:此图标记为pNews;


image.png

发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。

那消息转发是什么??????

  1. 动态方法解析
  2. 备用接收者
  3. 完整消息转发

动态方法解析:
例如:


image.png

viewDidLoad中执行run:方法,但是ViewController中根本未实现run:方法,
如果不做补救,不可避免的就是奔溃的下场....

那好,要怎么补救呢?

  1. import <objc/message.h>

  2. 实现resolveInstanceMethod:方法(这是返回BOOL的,应怎么返回呢?运用一句话,遇上对的人,勇敢说YES。上面提到SEL是Key, IMP要Value, 在没有IMP的情况下,首先选出SEL, 此例为@selector(run:), 找到后,即指定新的方法)
  3. 写新的c函数, 此例为void runMethod(id obj, SEL _cmd), 执行runMethod函数的IMP.
    执行结果如下:


    image.png

如果是下列情况呢?
这里不再添加新函数, 不可避免再次奔溃。


image.png

突然在想,+ (BOOL)resolveInstanceMethod:(SEL)sel ,返回YES和NO的区别在哪?上图pNews有写,如果返回YES, 那么就到了消息已处理了,那我强行转成YES, 是否可以标记消息已处理?
尝试过后,无语的发现,[ViewController run:]: unrecognized selector sent to instance 0x7f8786d14400'

image.png

大胆假设一下下,在执行run函数的时候,通过run函数这个SEL的key, 无法找到相应的IMP, 因此即使重写了+ (BOOL)resolveInstanceMethod:(SEL)sel, 返回了YES, 也没用,那么会走怎样的流程呢?

下一步就是找接收者了。

  • (id)forwardingTargetForSelector:(SEL)aSelector;//方法名的函义是:为选择器转发对象

这一步,要开始“造假对象”了。

“造假对象”??????
在Dog类中实现run方法,返回Dog对象,让Dog对象接收这个消息,

这时,控制台打印出Dog类执行run后的方法。

打印结果也证明我们成功实现了转发。

image.png
image.png
image.png

完整的消息转发流程如下:

image.png

其实省略以下两步完全不影响,这是因为
resolveInstanceMethod,返回YES/NO都不影响往下走流程
forwardingTargetForSelector一定返回nil。


image.png
  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector//给选择器的方法签名, 签名,是为了进入forwardInvocation方法

分析一下- (void)forwardInvocation:(NSInvocation *)anInvocation的实现

  1. 获取SEL
  2. 创建接收者,此例为Dog类型的实例d
  3. 如果d 响应SEL方法,实例d被调用去执行run:方法;否则,run:方法无法识别
image.png

Runtime应用
Runtime应用场景非常多,下面就介绍一些常见的应用场景。

  1. 关联对象(Objective-C Associated Objects)给分类增加属性
  2. 方法魔法(Method Swizzling)方法添加和替换和KVO实现
  3. 消息转发(热更新)解决Bug(JSPatch)
  4. 实现NSCoding的自动归档和自动解档
  5. 实现字典和模型的自动转换(MJExtension)

关联对象(Objective-C Associated Objects)

给分类添加方法是很常见的,这里就不展示了,这里只展示给分类添加属性。
可能有小伙伴立马来说,添加属性有什么好说的,随手就来一个。
然后立刻奔溃了。。。


image.png

image.png

分类中没有此新增属性的getter, setter方法,因此在调用getter时,会立马奔溃。

那是不是意味着不能支持添加属性?

不是,这时候就要到关联Objective-C Associated Objects了。


image.png
image.png
image.png

问题来了,怎样才能实现在分类中成功添加属性呢?


image.png

分析一下:
以上是实现getter, setter方法。

  1. import <objc/message.h>

  2. setter方法中objc_setAssociatedObject设置关联,
  3. getter方法, return objc_getAssociatedObject(self, "cpoName"); //注意"cpoName"就等同于字典的key, 要保持一致。
  4. Dog示例调用setter, getter --- cpoName
image.png
image.png

黑魔法 --- 方法魔法(Method Swizzling)方法添加
方法交换是很有优势的体现,例子如下


image.png

如果是TableView, 默认刷新数据的方法有reloaddata, 如果希望在reloaddata前做一些个性化操作,那么黑魔法无疑是很有优势的。
注意:替换的方法内部要自己调用自己,否则不实现,这样可以做自己的操作后,再自行刷新


image.png

此文部分内容参考了来源:https://www.jianshu.com/p/6ebda3cd8052

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

推荐阅读更多精彩内容

  • 2.Runtime储备知识 2.1.前言 在体验中,我写过这样一句话“并且我认为这个技术算是高阶开发里面一个...
    2897275c8a00阅读 581评论 1 2
  • 前言 什么是运行时(runtime)? 首先我们要先知道编程语言有静态和动态之分。所谓静态语言,就是在程序运行前决...
    夜_阑珊阅读 6,506评论 3 38
  • 前言 什么是运行时(runtime)? 首先我们要先知道编程语言有静态和动态之分。所谓静态语言,就是在程序运行前决...
    西门淋雨阅读 192评论 0 1
  • 本文主要整理了Runtime的相关知识。对于一个iOS开发者来说,掌握Runtime的重要性早已不言而喻。OC能够...
    梧雨北辰阅读 26,488评论 17 117
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,692评论 0 9