runtime第三部分方法和消息

时间:2023-01-06 10:14:35

接上一篇http://www.cnblogs.com/ddavidXu/p/5924049.html

转载来源http://www.jianshu.com/p/6b905584f536

http://southpeak.github.io/2014/10/30/objective-c-runtime-2/

方法和消息  OC中对象调用方法,实际是给对象发送消息

SEL又叫选择器,是表示一个方法的selector的指针,其定义如下:

typedef struct objc_selector *SEL;

objc_selector结构体的详细定义没有在头文件中找到。方法的selector用于表示运行时方法的名字

OC在编译时,会依据每一个方法的名字参数序列,生成唯一的整形标识(int类型的地址)这个标识就SEL

SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);
-- ::57.820 XDWRuntimeDemo[:] sel : 0x106d14bd9
  • 两个类之间,不管是不是父子关系,还是没有父子关系,只要方法名字相同,那个方法的SEL就是一样的;
  • 每一个方法都对应着一个SEL,所以在OC的同一个类(及继承体系中)中,不能同时存在2个同名的方法,即使参数类型不同也不可以。
  • 相同的方法只能对应一个SEL
  • 当然,不同的类可以拥有相同的selector,因为不同的实例对象执行相同的selector时,会在各自的方法列表中根据selector去找自己对应的IMP
  • 工程中所有的SEL组合成一个set集合,set特点就是唯一性,因此SEL是唯一的。当我们想到这个方法集合中查找某个方法时,只需要去找到这个方法对应的SEL就行了。
  • SEL实际上是根据方法名hash化了一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,速度非常非常的快,有一个问题就是,数量的增多会增大hash冲突而导致性能下降,将总量减少时最犀利的方法,为什么SEL仅仅是函数名。
  • 本质上SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的key值,能唯一代表一个方法),它的存在是为了加快方法的查询速度

我们可以通过runtime添加新的selector,也可以通过runtime获取已存在的selector,

sel_registerName函数

Objective-C编译器提供的@selector()

NSSelectorFromString()方法

IMP

imp实际上是一个函数指针,指向方法的实现首地址

id (*IMP)(id, SEL, ...)
  • 这个函数使用当前CPU架构实现的标准的C调用约定。第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),接下来是方法的实际参数列表。
  • 前面介绍过的SEL就是为了查找方法的最终实现IMP的。由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的 IMP,取得IMP后,我们就获得了执行这个方法代码的入口点,此时,我们就可以像调用普通的C语言函数一样来使用这个函数指针 了

通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些.

Method

typedef struct objc_method *Method;

struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}

我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码

struct objc_method_description { SEL name; char *types; };//方法描述

方法相关操作函数

// 调用指定方法的实现
id method_invoke ( id receiver, Method m, ... ); //method_invoke函数,返回的是实际实现的返回值。参数receiver不能为空。这个方法的效率会比method_getImplementation和method_getName更快。 // 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... ); // 获取方法名
SEL method_getName ( Method m ); //method_getName函数,返回的是一个SEL。如果想获取方法名的C字符串,可以使用sel_getName(method_getName(method)) // 返回方法的实现
IMP method_getImplementation ( Method m ); // 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m ); // 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );//类型字符串会被拷贝到dst中 // 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index ); // 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len ); // 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m ); // 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len ); // 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m ); // 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );//注意该函数返回值是方法之前的实现 // 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );

方法选择器

// 返回给定选择器指定的方法的名称
const char * sel_getName ( SEL sel ); // 在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
SEL sel_registerName ( const char *str ); //在我们将一个方法添加到类定义时,我们必须在Objective-C Runtime系统中注册一个方法名以获取方法的选择器 // 在Objective-C Runtime系统中注册一个方法
SEL sel_getUid ( const char *str ); // 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );

方法调用流程

在OC中,消息直到运行时才绑定到方法实现上,编译器会将消息表达式[receiver message]转化为一个消息函数的调用,即objc_msgSend.这个函数将消息接受者的方法名作为其基础参数。

objc_msgSend(receiver, selector)
objc_msgSend(receiver, selector, arg1, arg2, ...)

这个函数完成了动态绑定的所有事情:

1,首先它找到selector对应的方法实现IMP,因为同一个方法可能在不同的类中有不同的实现,所以我们需要依赖接收者的类来找到确切的实现。

2. 它调用方法实现,并将接收者对象及方法的所有参数传给它

3. 最后,它将实现返回的值作为它自己的返回值。

消息的关键在于结构体objc_class

struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif } OBJC2_UNAVAILABLE;

在这个结构体中,我们需要注意

1.指向父类的指针 isa

2.一个类的方法分发表 ,methodlists

创建对象的过程

创建对象-->分配内存-->初始化成员变量(isa指针也会被初始化)

当我们创建一个新对象时,先为其分配内存,并初始化其成员变量。其中isa指针也会被初始化,让对象可以访问类及类的继承体系

当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表里面查找方法的selector。如果 没有找到selector,则通过objc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector。依 此,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实 现。如果最后没有定位到selector,则会走消息转发流程,

消息发送给一个对象-->>objc_msgSend通过对象的isa指针获取到类的结构体-->在方法分发表里面查找方法的selector-->定位到selector,函数会就获取到了实现的入口点,
                                          | 并传入相应的参数来执行方法的具体实现
                                          |
  没有找到selector,则通过objc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector(知道NSObject类)
                                          |
                                          |
                              如果最后没有定位到selector,则会走消息转发流程,

为了加速消息的处理,运行时系统缓存使用过的selector及对应的方法的地址

隐藏参数

objc_msgSend有两个隐藏参数:

  1. 消息接收对象

  2. 方法的selector

这两个参数为方法的实现提供了调用者的信息。之所以说是隐藏的,是因为它们在定义方法的源代码中没有声明。它们是在编译期被插入实现代码的。

虽然这些参数没有显示声明,但在代码中仍然可以引用它们。我们可以使用self来引用接收者对象,使用_cmd来引用选择器。如下代码所示:

- strange
{
id target = getTheReceiver();
SEL method = getTheMethod(); if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}

获取方法地址

runtime方法中的动态绑定让我们写代码更具有灵活性,比如我们可以把消息转发给我们想要的对象,或者随意交换两个方法的实现等。灵活性的提升也带来了一定的性能耗损,毕竟要查找方法的实现,不像调用函数那么简单,不过方法缓存一定程度上解决了这个问题

MethodForSelector:方法可以获取方法的指针。

IMP str = [self methodForSelector:@selector(setFilled:)];

消息转发

当一个对象调用一个方法时,即给这个对象发送一个消息,假如这个对象无法接受这个消息,即这个对象对应的类,以及对应类的父类中都没有找到这个方法,正常情况下,object无法响应message,编译器会报错,崩溃。

- (void)doesNotRecognizeSelector:(SEL)aSelector {  //调用次方法崩溃
[super doesNotRecognizeSelector:aSelector];
}

但是如果使用perform的形式来调用方法,会等到运行时才能确定object是否能接收message消息,如果不能,则会崩溃。

(litttle tip)看一个对象是否能响应某个消息时进行检验

if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}

使用perform调用一个方法时,对象无法接受消息,就会启动消息转发机制,在程序崩溃前,我们有三次机会通过消息阻止程序崩溃。

消息转发机制基本上分为三个步骤:

  1. 动态方法解析

  2. 备用接收者

  3. 完整转发

对象接受未知消息--调用所属类的的类方法+resolveInstancheMethod:(实例方法)或者+resolveClassMethod:(类方法)--增加处理方法比如通过classMethod函数动态添

处理方法一(更多的是为了实现@dynamic属性)

void functionForMethod1(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
} + (BOOL)resolveInstanceMethod:(SEL)sel { NSString *selectorString = NSStringFromSelector(sel); if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
} return [super resolveInstanceMethod:sel];
}

处理方法二(当上一种方法中未做处理时,或处理失败,继续调用下面的方法)

- (id)forwardingTargetForSelector:(SEL)aSelector // 如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,这个对象不能是self自身,否则就会出现无线循环,
                               一般都调用父类的这个方法来实现返回结果

这一步适合我们将消息转发到另一个能处理该消息的对象上,但是无法对消息进行处理

@interface SUTRuntimeMethodHelper : NSObject

- (void)method2;

@end

@implementation SUTRuntimeMethodHelper

- (void)method2 {
NSLog(@"%@, %p", self, _cmd);
} @end #pragma mark - @interface SUTRuntimeMethod () {
SUTRuntimeMethodHelper *_helper;
} @end @implementation SUTRuntimeMethod + (instancetype)object {
return [[self alloc] init];
} - (instancetype)init {
self = [super init];
if (self != nil) {
_helper = [[SUTRuntimeMethodHelper alloc] init];
} return self;
} - (void)test {
[self performSelector:@selector(method2)];
} - (id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"forwardingTargetForSelector"); NSString *selectorString = NSStringFromSelector(aSelector); // 将消息转发给_helper来处理
if ([selectorString isEqualToString:@"method2"]) {
return _helper;
} return [super forwardingTargetForSelector:aSelector];
} @end

处理方法三(上一步还是不能处理消息,启动完整的消息转发机制)

- (void)forwardInvocation:(NSInvocation *)anInvocation  //NSInvocation对象   尚未处理的消息 有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数

可以实现一些更复杂的功能,内容修改追回参数等,如果发现某个消息不由本类处理,则调用父类的的同名方法,以便继承体系中每个类都有机会处理此调用请求

必须得重写以下方法

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

栗子

#import "Monkey.h"
#import "ForwardingTarget.h"
#import <objc/runtime.h> @implementation Monkey - (instancetype)init
{
self = [super init];
if (self) {
_target = [ForwardingTarget new];
[self performSelector:@selector(sel) withObject:@"yeyuyu"];//第一步,找不到这个方法的实现
} return self;
} - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// //3.第三次机会,调用这个方法,如果返回nil直接崩溃,返回函数签名,则会创建一个对象,执行相应的方法
// id result = [super methodSignatureForSelector:aSelector];
// NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
// result = sig;
// return result; // 3 //第三种情况的第二种写法
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
sig = [ForwardingTarget instanceMethodSignatureForSelector:aSelector];
}
return sig; } - (void)forwardInvocation:(NSInvocation *)anInvocation
{
// //3.返回函数签名后执行这个方法。执行相应的操作
// // [super forwardInvocation:anInvocation];
// anInvocation.selector = @selector(invocationTest);
// [self.target forwardInvocation:anInvocation]; //第三种情况的第二种写法
ForwardingTarget *new = [ForwardingTarget new];
if ([new respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:new];
} } @end #import "ForwardingTarget.h"
#import <objc/runtime.h> @implementation ForwardingTarget - (void)sel
{
//第二被转移到本类中之后会查找本类中是否有这个方法,这个类有就执行相应的方法
NSLog(@"ForwardingTarget");
} - (void)forwardInvocation:(NSInvocation *)anInvocation {
//3.进入这个方法中执行相应的方法
[self performSelector:anInvocation.selector withObject:nil];
// [super forwardInvocation:anInvocation];
} @end

消息转发可以达到类似多继承的效果,处理方法二和三,可以允许一个对象与其他对象建立关系,处理某些未知的消息。但是还是有一些区别,例如有的方法不能够用于转发链respondsToSelector:和isKindOfClass:

如果想让这种消息也看起来像继承,也可以重写这些方法。

小姐:实际开发中很少用到这些机制,但是是有助于我们更多的去了解底层的实现,实际编码中也可以更灵活的使用这些机制,实现一些特殊的功能,如hook操作等。

runtime第三部分方法和消息的更多相关文章

  1. Objective-C Runtime 运行时之三:方法与消息

    基础数据类型 SEL SEL又叫选择器,是表示一个方法的selector的指针,其定义如下: typedef struct objc_selector *SEL; objc_selector结构体的详 ...

  2. Objective-C Runtime 运行时之三:方法与消息&lpar;转载&rpar;

    前面我们讨论了Runtime中对类和对象的处理,及对成员变量与属性的处理.这一章,我们就要开始讨论Runtime中最有意思的一部分:消息处理机制.我们将详细讨论消息的发送及消息的转发.不过在讨论消息之 ...

  3. 理解Objective-C Runtime(三)消息转发机制

    消息转发机制概述 上一篇博客消息传递机制中讲解了Objective-C中对象的「消息传递机制」.本文需要讲解另外一个重要问题:当对象受到无法处理的消息之后会发生什么情况? 显然,若想令类能理解某条消息 ...

  4. iOS 高级开发 runtime(三)

    三 .动态添加方法 我们可以通过runtime动态地添加方法.那么到底啥叫动态添加方法呢?动态添加方法就是当我们程序运行时才知道我们应该调用哪个方法.我们首先需要了解这一点,当我们编写完一段代码后,我 ...

  5. iOS runtime探究&lpar;三&rpar;&colon; 从runtime開始理解OC的属性property

    你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639303 本文主要解说runtime相关知识, ...

  6. iOS runtime探究&lpar;二&rpar;&colon; 从runtime開始深入理解OC消息转发机制

    你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识, ...

  7. 转载:WinForm中播放声音的三种方法

    转载:WinForm中播放声音的三种方法 金刚 winForm 播放声音 本文是转载的文章.原文出处:http://blog.csdn.net/jijunwu/article/details/4753 ...

  8. mysql分表的三种方法

    先说一下为什么要分表当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间.根据个人经验,mysql执行一 ...

  9. 利用Objective-C运行时hook函数的三种方法

    版权声明:转载请注明出处:http://blog.csdn.net/hursing 方法一,hook已有公开头文件的类: 首先写一个Utility函数: #import <objc/runtim ...

随机推荐

  1. JavaScript 9种类型

    Undefined . Null . Boolean . String . Number . Object . Reference .List .Completion

  2. html的留言板制作(js)

    这次留言板运用到了最基础的localstorage的本地存储,展现的效果主要有: 1.编写留言2.留言前可以编辑自己的留言昵称.不足之处: 1.未能做出我喜欢的类似于网易的叠楼功能. 2.未能显示评论 ...

  3. Delphi线程简介---Create及其参数、Resume、Suspend

    TThread在Classes单元里的声明如下 type TThread = class private FHandle: THandle; FThreadID: THandle; FTerminat ...

  4. web&period;xml中servlet初始化参数的设置

    <context-param><param-name>param1</param-name><param-value>value1</param- ...

  5. otf字体转ttf字体

    可以使用Font creator进行转换字体. 绿色版下载链接 Font Creator(字体编辑软件下载)V9.0官方版 或者我的百度云:http://pan.baidu.com/s/1c1jjfm ...

  6. &lbrack;ACM&rsqb; hdu 4418 Time travel &lpar;高斯消元求期望&rpar;

    Time travel Problem Description Agent K is one of the greatest agents in a secret organization calle ...

  7. Linux常用快捷按键

    Linux常用快捷按键 为了提高工作效率 1 一定用快捷键 这里简单的说下几个常用的快捷按键. Ctrl + l    清屏,相当于clear命令. Ctrl + z    挂起,程序放到后台,程序没 ...

  8. Netty 学习 一、初识Netty【原创】

    在过去几年的工作和学习中,比较关注高层次的应用开发,对底层探究较少.实现Web应用的开发,主要依赖Tomcat.Apache等应用服务器,程序员无需了解底层协议,但同样限制了应用的性能和效率.现在开始 ...

  9. 学JAVA第十三天,方法、方法重载及构造函数

    今天终于不讲狗跳楼的问题了,今天讲了方法,方法重载及构造函数及构造函数重载的课程了. 这里说了有参好无参的,下面讲构造函数重载和方法重载. 其实,这上面写的这些方法,就相当一个模板.想要快速做出产品就 ...

  10. ElasticSearch(五):Java操作ElasticSearch执行查询

    package com.gxy.ESChap01; import java.net.InetAddress; import org.elasticsearch.action.search.Search ...