美文网首页iOS Developer
performSelector扩展

performSelector扩展

作者: 小曼blog | 来源:发表于2020-03-04 16:28 被阅读0次

    上一节《说一说基类 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;
    
    @end
    
    NS_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.png image.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} void
    target: {@} 0x0
    selector: {:} null
    argument 2: {@} 0x0
    argument 3: {@} 0x0
    argument 4: {@} 0x0
    
    (lldb) 
    

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

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


    image.png
    (lldb) po invocation
    <NSInvocation: 0x100713280>
    return value: {v} void
    target: {@} 0x102000b70
    selector: {:} sayHelloWithName:age:gender:
    argument 2: {@} 0x0
    argument 3: {@} 0x0
    argument 4: {@} 0x0
    
    (lldb) 
    

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

    继续往下走。


    image.png

    再次打印invocation。

    (lldb) po invocation
    <NSInvocation: 0x100713280>
    return value: {v} void
    target: {@} 0x102000b70
    selector: {:} sayHelloWithName:age:gender:
    argument 2: {@} 0x100002288
    argument 3: {@} 0xf85f415ba9fdb217
    argument 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

    三、NSThread中的perform

    在NSThread中,也有几个performSelector开头的方法,这几个方法又是干什么的呢?如何使用呢?我们来一起看看吧。
    首先,我们来观察一下这些方法。

    1.都没有返回值。
    有返回值的方法就不宜使用这种方法进行调用啦。
    2.都与线程有关
    不管是mainthread还是background还是子线程,这些方法都是与线程有着密切的关系。
    3.是NSObject分类中的方法
    也就是说,继承自NSObject的任何类都是可以调用这些方法的。

    我们来一一尝试吧。

    1.- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    基于默认模式,在主线程进行方法的调用。
    参数说明:
    aSelector:选择器,就是要执行的方法名字;
    arg:要传递的方法参数,对象类型。
    wait:是否阻塞当前线程直到指定选择器在主线程中执行完毕。选择YES会阻塞这个线程;选择NO,本方法会立刻返回。

    我们在main方法中,直接调用

        ClassA * a = [[ClassA alloc] init];
        [a performSelectorOnMainThread:@selector(sayHelloWithName:) withObject:@"小绵羊" waitUntilDone:YES];
    

    打印结果:


    image.png

    此方法常用语更新UI,我们还是在APP中试一试。
    我们新建一个APP测试程序,如下图所示:


    image.png

    类说明:
    TableController: 列表VC。
    TableViewModel: 列表的viewmodel,为了简化VC。
    TableCell:自定义的cell。
    DataModel:自定义Model。

    界面用storyboard进行实现,如下图:


    image.png

    部分代码:

    -(void)loadData {
        
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
           
            for (int i = 0; i<1000; i++) {
                DataModel * model = [[DataModel alloc] init];
                model.name = [self getName];
                model.className = [self getClassName];
                model.num = [NSString stringWithFormat:@"%d", (i+1)];
                [weakSelf.viewModel.datas addObject:model];
            }
            
            [weakSelf.viewModel performSelectorOnMainThread:@selector(refreshTable) withObject:nil waitUntilDone:YES];
        });
    }
    
    

    运行结果:


    image.png image.png

    工程代码地址:
    https://github.com/weiman152/iOSTestCode/tree/master/iOS/TestPerform

    源码地址:

    https://github.com/weiman152/iOSTestCode/tree/master/iOS/Lesson8-NSObject和运行时

    相关文章

      网友评论

        本文标题:performSelector扩展

        本文链接:https://www.haomeiwen.com/subject/nmehlhtx.html