第一部分:performSelector

一、performSelector调用和直接调用区别

下面两段代码都在主线程中运行,我们在看别人代码时会发现有时会直接调用,有时会利用performSelector调用,今天看到有人在问这个问题,我便做一下总结,

[delegate imageDownloader:self didFinishWithImage:image];
[delegate performSelector:@selector(imageDownloader:didFinishWithImage:)withObject:self withObject:image];

1、performSelector是运行时系统负责去找方法的,在编译时候不做任何校验;如果直接调用编译是会自动校验。如果imageDownloader:didFinishWithImage:image:不存在,那么直接调用 在编译时候就能够发现(借助Xcode可以写完就发现),但是使用performSelector的话一定是在运行时候才能发现(此时程序崩溃);Cocoa支持在运行时向某个类添加方法,即方法编译时不存在,但是运行时候存在,这时候必然需要使用performSelector去调用。所以有时候如果使用了performSelector,为了程序的健壮性,会使用检查方法- (BOOL)respondsToSelector:(SEL)aSelector;
2、直接调用方法时候,一定要在头文件中声明该方法的使用,也要将头文件import进来。而使用performSelector时候,可以不用import头文件包含方法的对象,直接用performSelector调用即可。
二、常用的performSelector简单分析
1.

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

这三个方法,均为同步执行,与线程无关,主线程和子线程中均可调用成功。等同于直接调用该方法。在需要动态的去调用方法的时候去使用。
例如:[self performSelector:@selector(test2)];与[self test2];执行效果上完全相同。
2.

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

这两个方法为异步执行,即使delay传参为0,仍为异步执行。只能在主线程中执行,在子线程中不会调到aSelector方法。可用于当点击UI中一个按钮会触发一个消耗系统性能的事件,在事件执行期间按钮会一直处于高亮状态,此时可以调用该方法去异步的处理该事件,就能避免上面的问题。
在方法未到执行时间之前,取消方法为:

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

注意:调用该方法之前或在该方法所在的viewController生命周期结束的时候去调用取消函数,以确保不会引起内存泄露。

拓展:

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

这个方法是单线程的,也就是说只有当前调用次方法的函数执行完毕后,selector方法才会被调用。

比如:

- (void)changeText:(NSString *)string
{label.text = string;NSLog(@"changeText:(NSString *)string");
}- (void)changePopoverSize
{   [self performSelector:@selector(changeText:) withObject:@"Happy aha" afterDelay:1];NSLog(@"changePopoverSize#####end");sleep(5);NSLog(@"changePopoverSize-----end");
}

执行结果(注意时间):

2015-08-17 17:14:06.697 awrbv[1973:f803] changePopoverSize#####end

2015-08-17 17:14:11.698 awrbv[1973:f803] changePopoverSize-----end

2015-08-17 17:14:11.701 awrbv[1973:f803] changeText:(NSString *)string

3.

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

这两个方法,在主线程和子线程中均可执行,均会调用主线程的aSelector方法;
如果设置wait为YES:等待当前线程执行完以后,主线程才会执行aSelector方法;
设置为NO:不等待当前线程执行完,就在主线程上执行aSelector方法。
如果,当前线程就是主线程,那么aSelector方法会马上执行。
注意:apple不允许程序员在主线程以外的线程中对ui进行操作,此时我们必须调用performSelectorOnMainThread函数在主线程中完成UI的更新。

拓展:

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

这个方法是单线程的,也就是说只有当前调用次方法的函数执行完毕后,selector方法才会被调用。

比如:

- (void)changeText:(NSString *)string
{label.text = string;NSLog(@"changeText:(NSString *)string");
}- (void)changePopoverSize
{   [self performSelector:@selector(changeText:) withObject:@"Happy aha" afterDelay:1];NSLog(@"changePopoverSize#####end");sleep(5);NSLog(@"changePopoverSize-----end");
}

执行结果(注意时间):

2015-08-17 17:14:06.697 awrbv[1973:f803] changePopoverSize#####end

2015-08-17 17:14:11.698 awrbv[1973:f803] changePopoverSize-----end

2015-08-17 17:14:11.701 awrbv[1973:f803] changeText:(NSString *)string

如果要想多线程的话,可以是使用

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
或者
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

代码如下:

- (void)changeText:(NSString *)string
{label.text = string;NSLog(@"changeText:(NSString *)string");
}- (void)changePopoverSize
{[self performSelectorOnMainThread:@selector(changeText:) withObject:@"Happy aha111" waitUntilDone:YES];NSLog(@"changePopoverSize#####end");sleep(5);NSLog(@"changePopoverSize-----end");
}

执行结果如下:

2015-08-17 17:19:29.618 awrbv[2024:f803] changeText:(NSString *)string

2015-08-17 17:19:29.619 awrbv[2024:f803] changePopoverSize#####end

2015-08-17 17:19:34.620 awrbv[2024:f803] changePopoverSize-----end

可以看出,如果waitUntilDone:YES那么等changeText执行完毕后再往下执行

如果waitUntilDone:NO的话,结果如下:

2015-08-17 17:21:12.135 awrbv[2049:f803] changePopoverSize#####end

2015-08-17 17:21:17.137 awrbv[2049:f803] changePopoverSize-----end

2015-08-17 17:21:17.139 awrbv[2049:f803] changeText:(NSString *)string

4.

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

调用指定线程中的某个方法。分析效果同3。
5.

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

开启子线程在后台运行
三、示例
首先建立一个简单的函数

- (void) fooNoInputs {NSLog(@"Does nothing");
}

然后调用它[self performSelector:@selector(fooNoInputs)];
第二个试验看看如何在消息中传递参数
我们建立一个有input参数的函数

- (void) fooOneIput:(NSString*) first {NSLog(@"Logs %@", first);
}

然后调用它[self performSelector:@selector(fooOneInput:) withObject:@"first"];
第三个试验更多的参数

- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second {NSLog(@"Logs %@ then %@", first, second);
}

然后调用它

[self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second"];

第四个试验如何建立动态的函数,然后调用他们?我们需要建立一个selector

SEL myTestSelector = @selector(myTest:);
并且我们调用的函数在另外一个Class内
- (void)abcWithAAA: (NSNumber *)number {
int primaryKey = [number intValue];
NSLog("%i", primaryKey);
}
MethodForSelectors * mfs = [[MethodForSelectors alloc]init];
NSArray *Arrays = [NSArray arrayWithObjects:@"AAA", @"BBB", nil];
for ( NSString *array in Arrays ){
SEL customSelector = NSSelectorFromString([NSStringstringWithFormat:@"abcWith%@:", array]);
mfs = [[MethodForSelectors alloc] performSelector:customSelector withObject:0];
}

1.如果使用了ARC会产生“performSelector may cause a leak because its selector is unknown”警告
2.这种方式当传入一个不符合约定的消息时会继续运行并不报错。例如应该传入2个参数,但只传入1个参数。或传入了3个参数,第三个参数不会被初始化。
还有一种调用其他Class Function的方法是,但是不能有参数,我们这里假设没有参数,那么就可以这样[mfs customSelector]; 
完整的代码:

@implementation ClassForSelectors
- (void) fooNoInputs {
NSLog(@"Does nothing");
}
- (void) fooOneIput:(NSString*) first {
NSLog(@"Logs %@", first);
}
- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second {
NSLog(@"Logs %@ then %@", first, second);
}
- (NSArray *)abcWithAAA: (NSNumber *)number {
int primaryKey = [number intValue];
NSLog("%i", primaryKey);
}
- (void) performMethodsViaSelectors {
[self performSelector:@selector(fooNoInputs)];
[self performSelector:@selector(fooOneInput:) withObject:@"first"];
[self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second"];
}
- (void) performDynamicMethodsViaSelectors {
MethodForSelectors * mfs = [MethodForSelectors alloc];
NSArray *Arrays = [NSArray arrayWithObjects:@"AAA", @"BBB", nil];
for ( NSString *array in Arrays ){
SEL customSelector = NSSelectorFromString([NSStringstringWithFormat:@"abcWith%@:", array]);
mfs = [[MethodForSelectors alloc] performSelector:customSelector withObject:0];
}
}
@end
@implementation MethodForSelectors
- (void)abcWithAAA: (NSNumber *)number {
NSLog("%i", number);
}
@end

第二部分:forwardInvocation

一,消息发送
在Objective-C中,使用对象进行方法调用是一个消息发送的过程(Objective-C采用“动态绑定机制”,所以所要调用的方法直到运行期才能确定)。例如:

id returnValue = [someObject messageName:parameter];

其中,someObject是消息的“接收者”,messageName为“选择子”,“选择子”与参数合起来叫做消息。
当编译器看到消息后,会将其转为一条标准的C语言函数调用objc_msgSend,其原型如下:

void objc_msgSend (id self, SEL cmd, ...)

这是一个参数可变的函数,第一个参数是接受者,第二个参数是选择子,后续参数就是消息中的那些参数。objc_msgSend会搜索接收者类中的“方法列表”(选择子的名称是查表时所用的“键”)。如果能找到与选择子名称相符的方法,就跳转至其实现代码。如果找不到,就沿着起继承体系向上查找。如果最终还是没有找到相符的方法,那就执行“消息转发”。

二,尾部调用优化

一般情况下,在方法内部调用另外一个方法,就会把方法的内部变量、返回的地址等信息压入栈中。以便另外一个方法调用结束后,能够正确返回并处理。所以,如果A方法调用B方法,B方法调用C方法,C方法调用D方法⋯⋯,就需要往栈中压入许多信息。这样就可能发生栈溢出的情况。
为了在一定程度上避免这种情况发生,当方法是在最后一步执行另外一个方法的调用时,编译器会执行尾部调用优化。即不保留外层方法的信息(因为返回地址、内部变量等信息都不会再用到了),直接用内层方法的调用记录,取代外层方法的调用记录。
三,消息转发
当对象收到无法解读的消息时,就会启动“消息转发”机制。开发者可以在此过程中,通过函数对象处理未知的消息。

消息转发处理流程

上图显示了消息转发的具体流程,接收者在每一步中均有机会处理消息。步骤越往后处理消息的代价越大。

首先,会调用+ (BOOL)resolveInstanceMethod:(SEL)sel。若方法返回YES,则表示可以处理该消息。在这个过程,可以动态地给消息增加方法。代码如下图所示:

+ (BOOL)resolveInstanceMethod:(SEL)sel{NSString *selStr = NSStringFromSelector(sel);if ([selStr isEqualToString:@"name"]) {class_addMethod(self, sel, (IMP)nameGetter, "@@:");return YES;}if ([selStr isEqualToString:@"setName:"]) {class_addMethod(self, sel, (IMP)nameSetter, "v@:@");return YES;}return [super resolveInstanceMethod:sel];}void nameSetter(id self, SEL cmd, id value){NSString *fullName = value;NSArray *nameArray = [fullName componentsSeparatedByString:@" "];PersonModel *model = (PersonModel *)self;model.firstName = nameArray[0];model.lastName  = nameArray[1];
}
id nameGetter(id self, SEL cmd){ PersonModel *model = (PersonModel *)self;NSMutableString *name = [[NSMutableString alloc] init];if (nil != model.firstName) {[name appendString:model.firstName];[name appendString:@" "];}if (nil != model.lastName) {[name appendString:model.lastName];}return name;
}

若方法返回NO,则进行消息转发的第二步,查找是否有其它的接收者。对应的处理函数是:- (id)forwardingTargetForSelector:(SEL)aSelector。可以通过该函数返回一个可以处理该消息的对象。

- (id)forwardingTargetForSelector:(SEL)aSelector{  NSString *selStr = NSStringFromSelector(aSelector);if ([selStr isEqualToString:@"companyName"]) {return self.companyModel;}else{return [super forwardingTargetForSelector:aSelector];}
}

若第二步返回nil,则进入消息转发的第三步。调用- (void)forwardInvocation:(NSInvocation *)anInvocation。这个方法实现得很简单。只需要改变调用目标,使消息在新目标上得以调用即可。不过,如果采用这种方式,实现的效果与第二步的消息转发是一致的。所以比较有用的实现方式是:先以某种方式改变消息内容,比如追加另外一个参数,或者改换选择子,等等。例如:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{                          NSMethodSignature *sig = nil;NSString *selStr = NSStringFromSelector(aSelector);if ([selStr isEqualToString:@"deptName"]) {//此处返回的sig是方法forwardInvocation的参数anInvocation中的methodSignaturesig = [self.companyModel methodSignatureForSelector:@selector(deptName:)];}else{sig = [super methodSignatureForSelector:aSelector];}return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{NSString *selStr = NSStringFromSelector(anInvocation.selector);if ([selStr isEqualToString:@"deptName"]) {[anInvocation setTarget:self.companyModel];[anInvocation setSelector:@selector(deptName:)];BOOL hasCompanyName = YES;//第一个和第一个参数是target和sel[anInvocation setArgument:&hasCompanyName atIndex:2];[anInvocation retainArguments];[anInvocation invoke];}else{[super forwardInvocation:anInvocation];}
}

如果直到NSObject,继承体系中的其它类都无法处理这个消息转发,就会由NSObject调用该方法,并在该方法中调用doesNotRecognizeSelector,以抛出异常。

注意:

1.-forwardInvocation:在一个对象无法识别消息之前调用,再需要重载-methodSignatureForSelector:,因为在调用-forwardInvocation:之前是要把消息打包成NSIvocation对象的,所以需要-methodSignatureForSelector:重载,如果不能在此方法中不能不为空的NSMethodSignature对象,程序依然会崩溃

Demo:http://download.csdn.net/download/lxl_815520/9502065

performSelector和forwardInvocation之消息的派发和转发相关推荐

  1. android触摸消息的派发过程

    1.触摸消息是消息获取模块直接派发给应用程序的. 2.触摸消息在处理时, 需要根据触摸坐标计算该消息应该派发给哪个View/ViewGroup, 在案件取消处理中不存在 该计算过程. 3.没有类似&q ...

  2. Sms开源短信及消息转发器,不仅只转发短信,备用机必备神器

    Sms开源短信及消息转发器,不仅只转发短信,备用机必备神器. 短信转发器--不仅只转发短信,备用机必备神器! 监控Android手机短信.来电.APP通知,并根据指定规则转发到其他手机:钉钉群自定义机 ...

  3. C++中消息自动派发之一 About JSON

    1. 闲序 游戏服务器之间通信大多采用异步消息通信.而消息打包常用格式有:google protobuff,facebook thrift, 千千万万种自定义二进制格式,和JSON.前三种都是二进制格 ...

  4. 触摸消息总体派发过程

    和按键派发类似,当消息获取模块通过pipe将消息传递到客户端, InputQueue中的next()函数内部 调 用 nativePollOnce()函数中会读取该消息.如果有消息,则 回 调View ...

  5. Runtime消息动态解析与转发流程

    先上图: 下面根据具体代码看这张图. 一.创建一个Person类, Person.h #import <Foundation/Foundation.h>@interface Person ...

  6. python数据库抓取并保存_python:微信消息抓取、转发和数据库存储及源码

    前言 python的强大在于丰富的类库,经常会看到几行代码就可以实现非常强大的功能.它可以做爬虫.AI.自动化测试.小工具(抢票.抓包.微信消息抓取)等等. 本次我们来讲讲怎么来抓取微信消息?抓取微信 ...

  7. spring集成mina 实现消息推送以及转发

    spring集成mina: 在学习mina这块时,在网上找了很多资料,只有一些demo,只能实现客户端向服务端发送消息.建立长连接之类.但是实际上在项目中,并不简单实现这些,还有业务逻辑之类的处理以及 ...

  8. RabbitMQ入门-消息派发那些事儿

    在上篇<RabbitMQ-高效的Work模式>中,我们了解了Work模型,该模型包括一个生产者,一个消息队列和多个消费者. 我们已经通过实例看出消息队列中的消息是如何被一个或者多个消费者消 ...

  9. 模态对话框和非模态对话框的消息循环

    1.非模态对话框和父窗口共享当前线程的消息循环 2.模态对话框新建一个新的消息循环,并由当前消息循环派发消息,而父窗口.模态对话框屏蔽了用户对它父窗口的操作,但是不是在消息循环里面屏蔽,所以给父窗口发 ...

最新文章

  1. tl-wn821n无线网卡驱动 linux,tl-wn821n win10驱动
  2. 报头中的偏移量作用_C语言中函数的实现
  3. flower.php,flower.php
  4. PHP 会话(session 时间设定)使用入门
  5. 4-算法 与7无关的数最近的提交
  6. 罗永浩语出惊人怼iPhone;人机大战柯洁再败;三星深圳工厂整体裁撤| CSDN极客头条...
  7. 12.Nginx 功能
  8. Ka的回溯编程练习 Part4|分配工作与选书
  9. 读取wav文件中的音频数据操作
  10. macOS 启用ftp/telnet/tftp 服务
  11. 十五个Python经典案例,学会这些,Python基础已过关!
  12. Shiro 实战教程(上)
  13. python tolist()函数
  14. powerdesigner与mysql_powerdesigner、mysql
  15. 在字符串指定位置插入字符串C语言版
  16. 广告投放策略及数据分析
  17. USB数据端子 type-A/B/C
  18. 学大伟业:2019年学习生物竞赛究竟从何下手?
  19. 竞赛题B:股市风云。
  20. 数据泄露的类型以及如何防止它们

热门文章

  1. html:时钟 <script>
  2. 2008.05.14 地震--灾难--捐款
  3. Arduino EEPROM对结构体数据存储和读取
  4. Android VelocityTracker 滑动速度追踪
  5. 微信小程序笔记(三)Wepy使用记录—Stylus预编译基本使用
  6. 新年礼盒营销背后,是大企的逐利与流量竞争
  7. html怎么添加class属性值,原生JS给元素添加class属性(转QiaoZhi)
  8. 大数据挖掘分析工具集
  9. android 仿照支付宝蚂蚁森林
  10. scrapy爬虫初探