具体代码,见Runtime 笔记

Objective-C 方法的本质是:给接收者发送消息

如果消息接收者能够找到对应的 selector,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或者临时向接收者动态添加这个 selector 对应的实现内容,要么干脆玩完崩溃掉.

运行时能做什么?主要有下面几个作用:

  1. 创建、修改、自省 classes 和 objects
  2. 消息分发

运行时会发消息给对象,一个对象的 class 保存了方法列表,这些消息是如何映射到方法的?这些方法是如何被执行的?
第一个问题,class 的方法列表其实是一个字典,key 为 selectors , IMPs 为 value.一个 IMP 是指向方法在内存中的实现.很重要的一点是,selector 和 IMP 之间的关系是在运行时才决定的.而不是编译时,这样我们就可以玩出一点花样.
IMP 通常是指向方法的指针,第一个参数是 self ,类型为 id;第二个参数是 _cmd,类型为 SEL,余下的是方法的参数.这也是 self 和 _cmd 被定义的地方.

- (id)doSomethingWithInt:(int)aInt { }
id doSomethingWithInt (id self, SEL _cmd, int aInt) { }

Objective-c 在三种层面上与 Runtime 系统进行交互:
1.通过 Objective-C 源代码
2.通过 Foundation 框架的 NSObject 类定义的方法
3.通过对 Runtime 库函数的直接调用


IMP
在 objc.h 中的定义是:
typedef id( *IMP) (id ,SEL, …);
它就是一个函数指针,这是由编译器生成的.当你发起一个 Objc 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的.而 IMP 这个函数指针就指向了这个方法的实现.
如果得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面 Cache 会提到.
你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id 和 SEL 类型.每个方法都对应一个 SEL 类型的方法选择器,每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id 和 SEL 参数就能确定唯一的方法实现地址.
而一个确定的方法也只有唯一的一组 id 和 SEL 参数


Cache

typedef struct objc_cache *Cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets [] OBJC2_UNAVAILABLE;
}

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找.
Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候效率更高
runtime 如何找到方法
消息机制原理:对象根据方法编号 SEL 去映射表查找对应的方法实现


找不到方法时怎么转发
消息接收者没有找到对应的方法时候,会先调用此方法,可在此方法视线中动态添加新的方法
如果返回 NO 或者直接返回了 YES 而没有添加新的方法,该方法被调用


消息发送步骤:

1. 检测这个 selector 是不是要忽略的
2. 检测这个 target 是不是 nil 对象.Objective - C 的特性是允许对一个 nil 对象执行任何一个方法不会 crash ,因为会被忽略掉
3. 如果 1、2 都过了,那就开始查找这个类的 IMP, 先从 cache 里面找,找得到就跳到对应的函数去执行
4. 如果 cache 找不到就去 class 的方法列表找
5. 如果 class 中的方法列表找不到就去父类的方法列表找,一直找,直到找到 NSObject 类为止
6. 如果还找不到就要开始进入动态方法解析了


动态方法解析
1. 当一个消息发送过程中,如果找不到对应方法的实现,便会进行动态方法解析,可让我们动态绑定方法实现 例子:声明一个方法不实现就直接调用
2. 正常情况下,由于没有方法实现,程序奔溃.然而,我们可以通过分别重载 resolveInstanceMethod: 和 resolveClassMethod: 方法分别添加实例方法和类方法实现
3. 因为 runtime 系统在 Cache 中和 Class 的方法列表(包括父类)中找不到要执行的方法是, Runtime 会调用 resolveInstanceMethod: 或 resolveClassMethod: 来给程序员一次动态添加方法实现的机会

+(BOOL)resolveInstanceMethod:(SEL)aSEL {
if (aSEL == @selector(xxx1) {
class_addMethod([self class], aSEL, (IMP)xxx2, “v”); //参数是 Type Encoding, v = void,若 i = int, 官方文档 https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
return YES;
}
return [super resolveInstanceMethod:aSEL];
}

动态方法解析的前提:
前提是没有找到对应方法的实现, runtime 才会调用 resolveInstanceMethod: 或 resolveClassMethod:
如果 responseToSelector: 或 instancesRespondToSelector: 方法被执行,动态方法解析器将会被首先给予一个提供方法选择器对应的 IMP 的机会
动态方法解析会在消息转发机制浸入前执行,如果你想让该方法选择器被传送到转发机制,那么就让 resolveInstanceMethod: 返回 NO


消息转发
一、重定向
* 在消息转发机制执行前,runtime 系统会再给我们一次偷梁换柱的机会,即通过重载 - (id)forwardingTargetForSelector:(SEL)aSEL 方法替换消息的接收者为其他对象
* 前提是,先让 resolveInstanceMethod: 返回 NO ,才会被调用 -(id)forwardingTargetForSelector:(SEL)aSEL, 毕竟消息转发要耗费更多时间,抓住这次机会将消息重定向给别人是不错的选择
如果此方法返回 nil 或 self, 则会进入消息转发机制 forwardInvocation: ,否则将向返回的对象重新发送消息

二、转发
当动态方法解析不作任何处理返回 NO 时,则会调用 forwardingTargetForSelector 更改接收者,若返回 nil 或 self,消息转发机制会被触发.这时 forwardInvocation: 方法会被执行,可以重写这个方法来定义我们的转发逻辑
例子:

-(void)forwardInvocation:(NSInvocation *)anInvocation {
id someObject = [Object new];
if ([someObject respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:someObject];
}else{
[super forwardInvocation:anInvocation];
}
}

该消息的唯一参数是个 NSInvocation 类型的对象,该对象封装了原始的消息和消息的参数,我们可以实现 forwardInvocation: 方法来对不能处理的消息作一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误;
这里需要注意的是, anInvocation 参数是从哪里来的
其实在 forwardInvocation: 消息发送前,runtime 系统会像对象发送 methodSignatureForSelector: 消息,并取到返回的方法签名用于生成 NSInvocation 对象,所以在重写 forwardInvocation: 的同时也要重写 methodSignatureForSelector: 并返回不为空的 methodSignature, 否则会 crash

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
  if (aSelector == @selector(xxx)) {
// Type Encoding: v -> void @ -> id : -> SEL
return [NSMethodSignature signatureWithObjcTypes:”v@:”];
}else{
return [super methodSignatureForSelector:aSelector];
}
}

转发与继承相似,可以用于为 Objective - C 编程添加一些多继承的效果
尽管转发很想继承,但是 NSObject 类不会讲两者混淆,像 respondsToSelector: 和 isKindOfClass: 这类方法只会考虑继承体系,不会考虑转发链

iOS 学习 - 2.据网址显示源码的更多相关文章

  1. iOS学习——布局利器Masonry框架源码深度剖析

    iOS开发过程中很大一部分内容就是界面布局和跳转,iOS的布局方式也经历了 显式坐标定位方式 --> autoresizingMask --> iOS 6.0推出的自动布局(Auto La ...

  2. 《iOS开发指南》正式出版-源码-样章-目录,欢迎大家提出宝贵意见

    智捷iOS课堂-关东升老师最新作品:<iOS开发指南-从0基础到AppStore上线>正式出版了 iOS架构设计.iOS性能优化.iOS测试驱动.iOS调试.iOS团队协作版本控制.... ...

  3. 很值得学习的java 画图板源码

    很值得学习的java 画图板源码下载地址:http://download.csdn.net/source/2371150 package minidrawpad; import java.awt.*; ...

  4. 高仿114la网址导航源码完整最新版

    给大家本人我精心模仿的高仿114la网址导航源码,我们都知道114la网址导航的影响力,喜欢的朋友可以下载学习一下.  由于文件较大,没有上传了,下载地址在下面有的. 附源码下载: 114la网站导航 ...

  5. 访问php文件显示源码

    前天新装了个LAMP的环境,兴冲冲的clone下来代码,结果一访问乐子就大了,直接显现源码 面对这个问题,冥思苦想,四处找资料啊 让我改这改那的,最后终于找到症结 Ubuntu 16.04 系统 LA ...

  6. eclipse+jetty+web项目调试---不显示源码

    本人eclipse版本:JUNO 1.问题现象:显示源码时,不显示箭头(指示到哪行) 解决办法: debug configurations  --->Goals设置参数  clean -X je ...

  7. Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)

    前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...

  8. iOS新手引导页的实现,源码。

    /*.在Main.storyboard中找到,ScrollView和PageControl并添加到ViewController中. .在ScrollView中添加ImageView,新手引导页有几个图 ...

  9. Nginx学习笔记(六) 源码分析&启动过程

    Nginx的启动过程 主要介绍Nginx的启动过程,可以在/core/nginx.c中找到Nginx的主函数main(),那么就从这里开始分析Nginx的启动过程. 涉及到的基本函数 源码: /* * ...

随机推荐

  1. ADO.NET Entity Framework 在哪些场景下使用?

    在知乎回答了下,顺手转回来. Enity Framework已经是.NET下最主要的ORM了.而ORM从一个Mapping的概念开始,到现在已经得到了一定的升华,特别是EF等对ORM框架面向对象能力的 ...

  2. 如何使用yum 下载 一个 package ?如何使用 yum install package 但是保留 rpm 格式的 package ? 或者又 如何通过yum 中已经安装的package 导出它,即yum导出rpm?

    注意 RHEL5 和 RHEL6 的不同 How to use yum to download a package without installing it Solution Verified - ...

  3. 【译】Unity3D Shader 新手教程(2/6) —— 积雪Shader

    本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 如果你是一个shader编程的新手,并且你想学到下面这些酷炫的技术,我觉得你可以看看这篇教程: 实现一个积雪效果的 ...

  4. HTML5结构元素

    前面的话 几年前,用于网页布局的一般都用div元素,但语义化并不好.HTML5引入了大量新的块级元素来帮助提升网页的语义,使页面具有逻辑性的结构.容易维护,并且对数据挖掘服务更加友好.本文将详细介绍H ...

  5. ES6转换器之Babel

    ES6部分功能没有支持,所以想学习ES6,得先有个转换器,就是将ES6的代码转换为ES5. 我这里用的是Gulp + Bable的形式来将ES6转换为ES5的. 前提: (1).Gulp和Bable都 ...

  6. SQLServer:什么是主键(PK)和外键(FK)?

    一.主键与外键 1.主键是用来唯一地标识一行数据.主键列必须包含唯一的值,且不能包含空值(null). 2.主键可以建立在每张二维表中单列或者多列上. 3.一张二维表上的外键可以引用另一张二维表上对应 ...

  7. HBase框架学习之路

    1 背景知识 1.1 解决问题 解决HDFS不支持单条记录的快速查找和更新的问题. 1.2 适用情况 存在亿万条记录的数据库,只有千万或者百万条记录使用RDBMS更加合适 确保你的应用不需要使用RDB ...

  8. 计算机网络学习笔记--网络层之IP地址与子网

    IPv4地址: 我们知道在网络层(TCP/IP体系结构的网际互联层),最重要的一个协议就是IP协议,现在正处于IPv4和IPv6的过渡时期,但目前来说,IPv4仍为主流,所以主要讲Ipv4. IP地址 ...

  9. 在IE浏览器中执行OpenFlashChart的reload方法时无法刷新的解决方法

    由于项目需求,需要在网页上利用图表展示相关数据的统计信息,采用了OpenFlashChart技术.OpenFlashChart是一款开源的以Flash和Javascript为技术基础的免费图表,用它能 ...

  10. C#开发微信门户及应用(16)-微信企业号的配置和使用

    在本系列随笔的前面,主要就是介绍微信公众号的门户应用开发,最近把整个微信框架进行了扩展补充,增加了最新的企业号的API封装和开发,后续主要介绍如何利用C#进行微信企业号的开发工作,本篇作为微信企业号的 ...