具体代码,见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. BIO\NIO\AIO记录

    IO操作可以分为3类:同步阻塞(BIO).同步非阻塞(NIO).异步(AIO). 同步阻塞(BIO):在此种方式下,用户线程发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后, ...

  2. Linux Socket 原始套接字编程

    对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...

  3. zone.js - 暴力之美

    在ng2的开发过程中,Angular团队为我们带来了一个新的库 – zone.js.zone.js的设计灵感来源于Dart语言,它描述JavaScript执行过程的上下文,可以在异步任务之间进行持久性 ...

  4. 【初探Spring】------Spring IOC(二):初始化过程---简介

    首先我们先来看看如下一段代码 ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultList ...

  5. Windows 10 部署Enterprise Solution 5.5

    Windows 10正式版发布以后,新操作系统带来了许多的变化.现在新购买的电脑安装的系统应该是Windows 10.与当初用户不习惯Windows 7,购买新电脑后第一个想做的事情就是重装成XP,估 ...

  6. 【Win 10应用开发】提供建议列表的输入控件(AutoSuggestBox)

    AutoSuggestBox控件与TextBox控件相似,但,AutoSuggestBox控件可以提供一个下拉列表,用户可以从弹出的下拉列表中选择一个项,并把被选项的内容显示在输入框上.就类似于搜索引 ...

  7. 纪录我的iOS学习之路

    学习资料的网址 田伟宇(Casa Taloyum)有几篇介绍iOS架构的文章,一级棒!原博客链接. iOS应用架构谈 开篇 iOS应用架构谈 view层的组织和调用方案 iOS应用架构谈 网络层设计方 ...

  8. 【译】Unity3D Shader 新手教程(1/6)

    本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 刚开始接触Unity3D Shader编程时,你会发现有关shader的文档相当散,这也造成初学者对Unity3D ...

  9. 判断CPU是大端还是小端模式

    在小端模式中,低位字节放在低地址,高位字节放在高地址:在大端模式中,低位字节放在高地址,高位字节放在低地址.big-endian和little-endian,51单片机是典型的大端模式,Intel电脑 ...

  10. .NET Core的文件系统[4]:由EmbeddedFileProvider构建的内嵌(资源)文件系统

    一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以统一的编程方式来读取内嵌于某个程序集中的资源文件,不过在这之前我们必须知道如何将一个项目文 ...