iOS 学习 - 2.据网址显示源码
具体代码,见Runtime 笔记
Objective-C 方法的本质是:给接收者发送消息
如果消息接收者能够找到对应的 selector,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或者临时向接收者动态添加这个 selector 对应的实现内容,要么干脆玩完崩溃掉.
运行时能做什么?主要有下面几个作用:
- 创建、修改、自省 classes 和 objects
- 消息分发
运行时会发消息给对象,一个对象的 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.据网址显示源码的更多相关文章
- iOS学习——布局利器Masonry框架源码深度剖析
iOS开发过程中很大一部分内容就是界面布局和跳转,iOS的布局方式也经历了 显式坐标定位方式 --> autoresizingMask --> iOS 6.0推出的自动布局(Auto La ...
- 《iOS开发指南》正式出版-源码-样章-目录,欢迎大家提出宝贵意见
智捷iOS课堂-关东升老师最新作品:<iOS开发指南-从0基础到AppStore上线>正式出版了 iOS架构设计.iOS性能优化.iOS测试驱动.iOS调试.iOS团队协作版本控制.... ...
- 很值得学习的java 画图板源码
很值得学习的java 画图板源码下载地址:http://download.csdn.net/source/2371150 package minidrawpad; import java.awt.*; ...
- 高仿114la网址导航源码完整最新版
给大家本人我精心模仿的高仿114la网址导航源码,我们都知道114la网址导航的影响力,喜欢的朋友可以下载学习一下. 由于文件较大,没有上传了,下载地址在下面有的. 附源码下载: 114la网站导航 ...
- 访问php文件显示源码
前天新装了个LAMP的环境,兴冲冲的clone下来代码,结果一访问乐子就大了,直接显现源码 面对这个问题,冥思苦想,四处找资料啊 让我改这改那的,最后终于找到症结 Ubuntu 16.04 系统 LA ...
- eclipse+jetty+web项目调试---不显示源码
本人eclipse版本:JUNO 1.问题现象:显示源码时,不显示箭头(指示到哪行) 解决办法: debug configurations --->Goals设置参数 clean -X je ...
- Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)
前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...
- iOS新手引导页的实现,源码。
/*.在Main.storyboard中找到,ScrollView和PageControl并添加到ViewController中. .在ScrollView中添加ImageView,新手引导页有几个图 ...
- Nginx学习笔记(六) 源码分析&启动过程
Nginx的启动过程 主要介绍Nginx的启动过程,可以在/core/nginx.c中找到Nginx的主函数main(),那么就从这里开始分析Nginx的启动过程. 涉及到的基本函数 源码: /* * ...
随机推荐
- 一步步学习javascript基础篇(9):ajax请求的回退
需求1: ajax异步请求 url标识请求参数(也就是说复制url在新页面打开也会是ajax后的效果) ajax异步请求没问题,问题一般出在刷新url后请求的数据没了,这就是因为url没有记录参数.如 ...
- mvc4 自定义HtmlHelper
好久没写博客了,最近只看博客不写的习惯很不好啊. 好了,最近的项目中大量的用到了表单,很多表单有特殊的编写,但是在该项目中又有很多重复的地方,这个时候若能封装成htmlhelper将大大降低工作量的. ...
- 关于JavaScript中的delete操作
关于JavaScript中的delete操作 看到一道题,是这样的: (function(x){ delete x; return x; })(1); 1 null undefined Error 我 ...
- 搭建一个简单的mybatis框架
一.Mybatis介绍 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以 ...
- oracle11g 重装操作系统后,如何利用原有oracle表空间文件还原数据库
最近由于系统重装,在还原dmp备份文件时,由于数据原因(用exp命令导出时表没有导出全部),导致系统不能正常运行.根据网上的信息和个人实际情况,做个记录,便于后用. oracle 导出空表方法: 1. ...
- 同步与异步 & 阻塞与非阻塞
在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 一.同步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用 ...
- iOS手势解锁、指纹解锁--Swift代码
一.手势密码 1. 1.1.用UIButton组成手势的节点. 1.2.当手指接触屏幕时,调用重写的 touchesBegan:withEvent方法(在touchesBegan里调用setNeeds ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(47)-工作流设计-补充
系列目录 补充一下,有人要表单的代码,这个用代码生成器生成表Flow_Form表的Index代码就可以 加上几个按钮就可以了 <div class="mvctool"> ...
- Net中的常见的关键字
Net中的关键字有很多,我们最常见的就有new.base.this.using.class.struct.abstract.interface.is.as等等.有很多的,在这里就介绍大家常见的,并且有 ...
- 读书笔记--SQL必知必会20--管理事务处理
20.1 事务处理 使用事务处理(transaction processing),通过确保成批的SQL操作要么完全执行,要么完全不执行,来维护数据库的完整性. 如果没有错误发生,整组语句提交给数据库表 ...