performSelector扩展

作者 : 开心源码 本文共6032个字,预计阅读时间需要16分钟 发布时间: 2022-05-13 共179人阅读

上一节《说一说基类 NSObject(三)
》中我们学习了NSObject类中的三个方法:

image.png

简单回忆一下。
新建一个target,类ClassA,如下所示:

image.png

实现:

image.png

测试:

image.png

看看结果:

image.png

注意截图中,备注中的提醒。

从结果可以看出,方法都能够顺利调用。这是最基本的调用使用方法。

本节中,我们还将扩展一下performSelector的用法。我们发现,系统提供的三个方法,最多就带两个参数,我们有时候不仅仅是两个参数,可能是多个,该如何处理呢?

一、多个参数的使用方法

(1)利用数组传递
ClassA.h

-(void)sayHelloWithArray:(NSArray *)params;

ClassA.m

-(void)sayHelloWithArray:(NSArray *)params {        NSLog(@"数组: %@", params);}

测试

        //利用数组传递多个参数        [a performSelector:@selector(sayHelloWithArray:) withObject:@[@"小明",@"11",@"四年级”]];

结果:

image.png

(备注:为了打印数组的汉字,我写了一个NSArray的Category,重写了-(NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level 这个方法)

实际上,这并不算真正意义上的传递多个参数,仍然是传递的一个参数,不过是一个数组参数而已。

(2)给NSObject写一个分类,新建一个performSelector方法,传递多个参数。
首先,新建一个NSObject的分类。

image.png

而后,实现代码

#import <AppKit/AppKit.h>#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface NSObject (Perform)//传递多个参数-(id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects;@endNS_ASSUME_NONNULL_END

.m

////  NSObject+Perform.m//  Lesson8_4////  Created by wenhuanhuan on 2020/3/4.//  Copyright ? 2020 weiman. All rights reserved.//#import "NSObject+Perform.h”#import <AppKit/AppKit.h>@implementation NSObject (Perform)-(id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects {        /**     根据SEL去实例化方法签名NSMethodSignature(方法签名中有方法的名称,参数和返回值)     */    NSMethodSignature * sign = [[self class] instanceMethodSignatureForSelector:aSelector];    if (sign == nil) {        @throw [NSException exceptionWithName:@"签名异常" reason:@"没有这个方法" userInfo:nil];        return nil;    }        //根据方法签名拿到方法的信息    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:sign];    [invocation setTarget:self];    [invocation setSelector:aSelector];        //签名中方法参数的个数,内部包含了self和_cmd,所以参数从第3个开始    NSInteger paramCount = sign.numberOfArguments - 2;    NSInteger resultParamCount = MIN(paramCount, objects.count);        for (NSInteger i = 0; i < resultParamCount; i++) {        id object = objects[i];        [invocation setArgument:&object atIndex:i+2];    }    [invocation invoke];        //解决返回值    id callBack = nil;    if (sign.methodReturnLength > 0) {        [invocation getReturnValue:&callBack];    }    return callBack;}@end

测试:
在ClassA中再增加一个多参数的方法:

image.pngimage.png

在main中进行调用测试

//重写的performSelector,多参数传递        NSNumber * age = [NSNumber numberWithInt:20];        NSString * name = @"小土豆”;        NSString * gender = @“男”;        SEL selector = NSSelectorFromString(@"sayHelloWithName:age:gender:”);        NSArray * array = @[name, age, gender];        [a performSelector:selector withObjects:array];

先看看打印结果:

image.png

正确打印了结果。

我们来大致看看多参数传递的方法吧。

image.png

程序执行到这个方法中,先来看看参数,aSelector和objects都是正确传递过来了。

接着往下走,

image.png

打印一下sign,看看结果:

(lldb) po sign<NSMethodSignature: 0x100685970>    number of arguments = 5    frame size = 224    is special struct return? NO    return value: -------- -------- -------- ————        type encoding (v) ‘v’        flags {}        modifiers {}        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}        memory {offset = 0, size = 0}    argument 0: -------- -------- -------- ————        type encoding (@) ‘@‘        flags {isObject}        modifiers {}        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}        memory {offset = 0, size = 8}    argument 1: -------- -------- -------- ————        type encoding (:) ‘:’        flags {}        modifiers {}        frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}        memory {offset = 0, size = 8}    argument 2: -------- -------- -------- ————        type encoding (@) ‘@‘        flags {isObject}        modifiers {}        frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0}        memory {offset = 0, size = 8}    argument 3: -------- -------- -------- ————        type encoding (@) ‘@‘        flags {isObject}        modifiers {}        frame {offset = 24, offset adjust = 0, size = 8, size adjust = 0}        memory {offset = 0, size = 8}    argument 4: -------- -------- -------- ————        type encoding (@) ‘@‘        flags {isObject}        modifiers {}        frame {offset = 32, offset adjust = 0, size = 8, size adjust = 0}        memory {offset = 0, size = 8}(lldb) 

参数是5个。
这个对象中还包含返回值信息,参数详细信息(类型,内存大小等)。

继续往下走:

image.png

打印下invocation:

(lldb) po invocation<NSInvocation: 0x100713280>return value: {v} voidtarget: {@} 0x0selector: {:} nullargument 2: {@} 0x0argument 3: {@} 0x0argument 4: {@} 0x0(lldb) 

我们看到,返回值是void,目标对象target,选择器selector以及三个参数都是空的。

继续往下走,赋完值以后看看。

image.png

(lldb) po invocation<NSInvocation: 0x100713280>return value: {v} voidtarget: {@} 0x102000b70selector: {:} sayHelloWithName:age:gender:argument 2: {@} 0x0argument 3: {@} 0x0argument 4: {@} 0x0(lldb) 

我们看到,返回值为void,目标target有值为self,selector有值为sayHelloWithName:age:gender: 。都是我们刚刚赋的值。但是参数仍然为空,由于我们还没有给他赋值。

继续往下走。

image.png

再次打印invocation。

(lldb) po invocation<NSInvocation: 0x100713280>return value: {v} voidtarget: {@} 0x102000b70selector: {:} sayHelloWithName:age:gender:argument 2: {@} 0x100002288argument 3: {@} 0xf85f415ba9fdb217argument 4: {@} 0x1000022a8

参数都有值了。
执行完[invocation invoke];控制台打印出了我们想要打印的结果:

image.png

说明我们的方法是没有问题的。

注意:
由于NSArray中的元素都要求是对象,所以,仍然不能直接传递值类型的参数。

这种方法相当于自己实现了performSelector,也算是处理了多参数传递的问题吧。

二、RunLoop中,performSelector的其余方法

本节中,我们继续看一看performSelector系列的其余用法,这些方法没有定义在NSObject.h中,而是在另一个文件NSRunLoop.h中。

image.png

我们来试一试。

  1. -(void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
    推迟执行某个方法。

注意:
这个方法是一个异步方法,在我们的控制台main方法中调用是不会执行的。比方:

image.png

个人猜想,是由于主线程已经走完了,这个任务还没来得及执行呢,程序就结束了。
我们要在VC中进行测试。

image.png

test方法推迟了1秒执行了。

使用的时候肯定要注意,假如不需要推迟的时候,就是用NSObject中定义的三个performSelector方法,比较安全。

假如把这个方法放在另一个线程中也是会出问题的。

image.png

猜猜test会执行吗?
看看结果吧。

image.png

没错儿,它不会执行的。有人说,这是由于子线程的runloop默认是不开启的,需要开启runloop才会执行,来试试看。

image.png

再看看结果:

image.png

仍然没有打印test中的内容,说明没有执行。
所以在使用的时候肯定要注意,不要在子线程中使用这个方法。至于为什么不执行,我没有找到答案,也欢迎大神指教(肯定要有源码验证哟)。

2.- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;

此方法多了一个modes,也跟上面的方法一样,是个异步方法,放在main中,也是不会执行的。

image.png

同样,放在VC中,可以执行。

image.png

3.+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
取消执行某个推迟执行的方法。我们试一试,同样的,我们还得在VC中试。

image.png

我们有两个方法,一个是test,一个是hello,test在3秒后执行,hello在2秒后执行,而后我们又取消了test的执行,看看结果吧。

image.png

我们发现test被取消了,没有执行,hello在2秒后执行了。

4.+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
该方法与上一个方法相似,它取消的是所有的target中没有执行的方法。
为了更好的验证,我们多写几个方法,分别是3秒,2秒,0秒后执行,还有个直接执行的方法sing。而后,我们取消所有的未执行方法,看看结果。

image.png

结果如下:

image.png

测试发现,只有使用
-(void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
这个方法执行的任务才会被取消,即便是0秒后执行,也是会被取消的。个人以为,是由于上面这个方法是异步的,执行速度比较慢,所以会被取消。

以上四个方法都是RunLoop中定义的,推迟执行的两个方法是异步的,使用的时候肯定要注意。还有几个perform的方法,定义在NSThread中,如下图:

image.png

下次再试哟,未完待续······

源码地址:

weiman152/iOSTestCode/tree/master/iOS/Lesson8-NSObject和运行时

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » performSelector扩展

发表回复