具体代码,见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. NodeJs 开发微信公众号(三)微信事件交互

    微信公众号有个规则,一旦开启了开发者模式,其他的常规功能就都必须通过接口调用完成.比如说自定义菜单功能,必须通过发送post请求的方式生成.本章就通过关注到取消关注的整个过程来谈一谈nodejs是怎么 ...

  2. ASP.NET Web API WebHost宿主环境中管道、路由

    ASP.NET Web API WebHost宿主环境中管道.路由 前言 上篇中说到ASP.NET Web API框架在SelfHost环境中管道.路由的一个形态,本篇就来说明一下在WebHost环境 ...

  3. AngularJs之四(作用域)

    一:angulaJs的作用域scope Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带.scope 是一个 JavaScript 对象,带有属性和方 ...

  4. Hawk 1.2 快速入门2 (大众点评18万美食数据)

    本文将讲解通过本软件,获取大众点评的所有美食数据,可选择任一城市,也可以很方便地修改成获取其他生活门类信息的爬虫. 本文将省略原理,一步步地介绍如何在20分钟内完成爬虫的设计,基本不需要编程,还能自动 ...

  5. Python笔记之不可不知

    Python软件已经安装成功有很长一段时间了,也即或多或少的了解Python似乎也很长时间了,也是偏于各种借口,才在现在开始写点总结.起初接触Python是因为公司项目中需要利用Python来测试开发 ...

  6. Python基础(二)

    本章内容: Python 运算符(算术运算.比较运算.赋值运算.逻辑运算.成员运算) 基本数据类型(数字.布尔值.字符串.列表.元组.字典.set集合) for 循环 enumrate range和x ...

  7. 匹夫细说C#:从园友留言到动手实现C#虚函数机制

    前言 上一篇文章匹夫通过CIL代码简析了一下C#函数调用的话题.虽然点击进来的童鞋并不如匹夫预料的那么多,但也还是有一些挺有质量的来自园友的回复.这不,就有一个园友提出了这样一个代码,这段代码如果被编 ...

  8. [Spring]支持注解的Spring调度器

    概述 如果想在Spring中使用任务调度功能,除了集成调度框架Quartz这种方式,也可以使用Spring自己的调度任务框架. 使用Spring的调度框架,优点是:支持注解(@Scheduler),可 ...

  9. 前端开发之走进Vue.js

    Vue.js作为目前最热门最具前景的前端框架之一,其提供了一种帮助我们快速构建并开发前端项目的新的思维模式.本文旨在帮助大家认识Vue.js,了解Vue.js的开发流程,并进一步理解如何通过Vue.j ...

  10. 前端开发:Javascript中的数组,常用方法解析

    前端开发:Javascript中的数组,常用方法解析 前言 Array是Javascript构成的一个重要的部分,它可以用来存储字符串.对象.函数.Number,它是非常强大的.因此深入了解Array ...