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的启动过程. 涉及到的基本函数 源码: /* * ...
随机推荐
- Fedora 22中的用户和用户组管理
The control of users and groups is a core element of Fedora system administration. This chapter expl ...
- 自定义Sublime Text的图标
sublime text很赞,windows上最接近mac逼格的轻量编辑器,对于我这样比较喜欢格调的人来说,简直不二之选啊. 美中不足的是,看久了觉得它的图标似乎不是很上心.现在都流行扁平化了而它还停 ...
- ubuntu 启动MySql和安装python的MySQLdb模块
ubuntu一般会自己预安装mysql,你只需 /etc/init.d/mysql start|stop|restart|reload|force-reload|status 命令便可以实现mysq ...
- 快消品迎来B2B元年,行业将如何变革?
一年接近尾声,又到了年终总结的时候,宴会厅里传来各种激情澎湃的演讲,有的行业遍地开花.欢声笑语不绝于耳:有的行业却没能迎来"昨夜东风",只能嗟叹"不堪回首".2 ...
- 今天有群友不是很清楚htm直接存数据库的危害,我简单举个例子
通过这个案例就知道为什么不要把原生的html放数据库了 常见的几种转码 常用的几种显示方法 只有原生html和最下面一种弹框了,变成了持久xss 如果是Ajax的方式,请用@Ajax.JavaS ...
- 【解决方案】 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userHandler': Injection of resource dependencies failed;
一个错误会浪费好多青春绳命 鉴于此,为了不让大家也走弯路,分享解决方案. [错误代码提示] StandardWrapper.Throwableorg.springframework.beans.fac ...
- 设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)
在前两篇博客中详细的介绍了"策略模式"和“观察者模式”,今天我们就通过花瓶与鲜花的例子来类比一下“装饰模式”(Decorator Pattern).在“装饰模式”中很好的提现了开放 ...
- 填坑系列:通过ESXi来配置IPMI
近日西安的天气很不错,可是看到从其他地方迁移来的主机在新环境下无法远程调试怪郁闷的,这就需要填坑,要不就会给后来者挖更大的坑. 今天遇到的坑是在IPMI的网络设置里面启用了VLAN标签之后,在新环境下 ...
- Unity3D中使用委托和事件
前言: 本来早就想写写和代码设计相关的东西了,以前做2DX的时候就有过写写观察者设计模式的想法,但是实践不多.现在转到U3D的怀抱中,倒是接触了不少委托事件的写法,那干脆就在此总结一下吧. 1.C#中 ...
- NGUI学习笔记(一)UILabel介绍
来个前言: 作为一个U3D程序员,自然要写一写U3D相关的内容了.想来想去还是从UI开始搞起,可能这也是最直观同时也最重要的部分之一了.U3D自带的UI系统,也许略坑,也没有太多介绍的价值,那么从今天 ...