消息处理之performSelector

前面一篇文章介绍了runtime的知识,就是调用[Person run]的直接方法调用和performSelector有什么区别呢?

performSelector和直接调用方法的区别

performSelector和直接调用方法的区别就在于runtime。
直接调用编译时会自动校验。如果方法不存在,那么直接调用在编译时候就能够发现,编译器会直接报错。
使用performSelector的话一定是在运行时才能发现,如果此方法不存在就会崩溃。所以如果使用performSelector的话他就会有个最佳伴侣- (BOOL)respondsToSelector:(SEL)aSelector;来在运行时判断对象是否响应此方法。

performSelector的使用

线程无关的方法

可以看到在NSObject.h文件中:

  1. 无参数传递
  2. 传递一个参数
  3. 传递两个参数
1
2
3
- (id)performSelector:(SEL)aSelector;//无参数传递
- (id)performSelector:(SEL)aSelector withObject:(id)object;//传递一个参数
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;//传递两个参数

Delayed perform

在NSRunLoop.h中:

1
2
3
4
5
6
7
8
@interface NSObject (NSDelayedPerforming)

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

@end

1、前两个方法为异步执行
2、前两个方法只能在主线程中执行,其它线程不执行
3、即使delay传参为0,也不会立即执行,而是在next runloop执行
4、可用于当点击UI中一个按钮会触发一个消耗系统性能的事件,在事件执行期间按钮会一直处于高亮状态,此时可以调用该方法去异步的处理该事件,就能避免上面的问题。

在方法未到执行时间之前,取消方法为后两个方法

1
2
3
4
5
6
7
@interface NSRunLoop (NSOrderedPerform)

- (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes;
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg;
- (void)cancelPerformSelectorsWithTarget:(id)target;

@end

on mainthread

在NSThread.h中

1
2
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
  1. 这两个方法,在主线程和子线程中均可执行,均会在主线程中调aSelector方法
  2. 如果设置wait为NO:等待当前线程执行完以后,主线程才会执行aSelector方法;
  3. 设置为YES:不等待当前线程执行完,就在主线程上执行aSelector方法。
  4. 第二个方法使用默认的模式(NSDefaultRunLoopMode)。

传递三个及以上的参数

perform方法要求传入参数必须是对象,如果方法本身的参数是int,直接传NSNumber会导致得到的int参数不正确。

第一种:NSInvocation

使用了runtime的反射机制,效率较低,可读性不高

列举ASIHTTPRequest.m的例子:

1
2
3
4
5
6
7
8
9
10
SEL callback = @selector(performInvocation:onTarget:releasingObject:);
NSMethodSignature *cbSignature = [ASIHTTPRequest methodSignatureForSelector:callback];
// NSInvocation : 利用一个NSInvocation对象包装一次方法调用(方法调用者、方法名、方法参数、方法返回值)
NSInvocation *cbInvocation = [NSInvocation invocationWithMethodSignature:cbSignature];
[cbInvocation setSelector:callback];
[cbInvocation setTarget:self];
//前两个参数为self与selector,其它参数要从2开始
[cbInvocation setArgument:&invocation atIndex:2];
[cbInvocation setArgument:&target atIndex:3];
[cbInvocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:[NSThread isMainThread]];

第二种:把多个参数封装成一个参数

我们还可以将参数进行封装成一个结构体,Class,或者是字典和其他集合。可以把结构体转换为对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void) anotherMethod {
id target = self;
SEL selector = @selector(methodWithFirst:second:third:);
// 首先第一个参数和第二个参数是self和selector
// 剩下的参数可以是对象或者基本数据类型,根据方法需要填写参数类型
typedef void (*MethodType)(id, SEL, id, id, id);
// 这个方法实际上返回的是IMP类型,为了方便调用,强转为MethodType
MethodType methodToCall = (MethodType)[target methodForSelector:selector];
methodToCall(target, selector, @(1), @(2), @(3));
}
- (void)methodWithFirst:(id) object second:(id) secondObject third:(id) thirdObject {
// Code here
}

其他例子

例子1

例如在封装一个控件的时候,一个自定义的label的响应事件:
[obj.lblVal performSelector:NSSelectorFromString(@"removeGesture") withObject:nil];
对于自定义的封装控件lblVal来说,在它的实现中就有removeGesture的方法:

1
2
3
4
5
6
- (void)removeGesture {
if (self.gesture) {
[self removeGestureRecognizer: self.gesture];
self.gesture = nil;
}
}

例子2

在AppDelegate.m中

1
2
3
4
5
6
- (void)initAllFunctionSwitchDictionary
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"TDFFunctionSwitch" ofType:@"plist"];
NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:path];
[Platform Instance].allFunctionSwitchDictionary = dic;
}

从而加载TDFFunctionSwitch.plist文件,在这个文件中mediatorMethod对应着TDFMediator_TDFXXXViewController。
mediatorClass对应着TDFMediator+XXXModule。
在每一个模块中:

1
2
3
4
5
6
7
8
9
10
11
- (void)pushViewControllerWithCode:(NSString *)actionCode
{
NSDictionary *switchU = [Platform Instance].allFunctionSwitchDictionary[actionCode];
if (switchU[@"mediatorMethod"]) {
SEL action = NSSelectorFromString(switchU[@"mediatorMethod"]);
if ([[TDFMediator sharedInstance] respondsToSelector:action]) {
UIViewController *viewController = [[TDFMediator sharedInstance] performSelector:action withObject:nil];
[self.navigationController pushViewController:viewController animated:YES];
}
}
}

先获得SEL,获得每个对应的code中的mediatorMethod,利用performSelector,来跳转页面。从而实现的中间者模式。

例子3

线程间的通信:

在主线程更新view的高度:
[self performSelectorOnMainThread:@selector(updateInfoViewHeight) withObject:nil waitUntilDone:YES];

在主线程更新tableView的数据源:
[self.tabView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];

  1. 在指定线程中做事情:
    performSelector:onThread:withObject:waitUntilDone:
    performSelector:onThread:withObject:waitUntilDone:modes:
    在ASIHTTPRequest.m中:
1
2
3
4
5
- (void)start
{
[self setInProgress:YES];
[self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
}

waitUntilDone参数是个bool值,如果设置为NO,相当于异步执行,当前函数执行完,立即执行后面的语句。如果设置为YES,相当于同步执行,当前线程要等待Selector中的函数执行完后再执行。

  1. 在当前线程中做事情:
    performSelector:withObject:afterDelay:
    performSelector:withObject:afterDelay:inModes:

  2. 取消发送给当前线程的某个消息
    cancelPreviousPerformRequestsWithTarget:
    cancelPreviousPerformRequestsWithTarget:selector:object:

防止按钮多次点击

这种方式是在0.2秒内取消之前的点击事件,以做到防止多次点击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(void)completeClicked:(UIButton *)sender{
[[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClick:) object:sender];
[self performSelector:@selector(buttonClick:) withObject:sender afterDelay:0.2f];
}
这种方式是在点击后设为不可被点击的状态,1秒后恢复

-(void)buttonClicked:(id)sender{

self.button.enabled =NO;

[selfperformSelector:@selector(changeButtonStatus)withObject:nilafterDelay:1.0f];//防止重复点击

}

-(void)changeButtonStatus{

self.button.enabled =YES;
}

Demo:https://github.com/peilinghui/BokeDemo/tree/master/PLHPerformDemo

文章目录
  1. 1. performSelector和直接调用方法的区别
  2. 2. performSelector的使用
    1. 2.1. 线程无关的方法
    2. 2.2. Delayed perform
    3. 2.3. on mainthread
    4. 2.4. 传递三个及以上的参数
      1. 2.4.1. 第一种:NSInvocation
      2. 2.4.2. 第二种:把多个参数封装成一个参数
  3. 3. 其他例子
    1. 3.0.1. 例子1
    2. 3.0.2. 例子2
    3. 3.0.3. 例子3
    4. 3.0.4. 防止按钮多次点击
本站总访问量 本站访客数人次 ,