美文网首页
iOS-performSelector实现多个参数

iOS-performSelector实现多个参数

作者: 凉风起君子意如何 | 来源:发表于2021-08-04 15:29 被阅读0次

    其实在日常iOS开发中,真正使用invocation的地方还是很少,趁着这段时间公司项目空闲期,对之前的面试题做个简单回忆总结。
    怎么实现performSelector支持2个以上的参数?
    说说对isa指针的理解?
    常见的crash?说说unrecognize selector to instance 具体发生过程
    以上,其实都涉及到方法具体是如何调用执行的。以下都是对问题一做个简单说明使用

    使用一:实现performSelector传入>2参数

    OC里常用调用方法的几种方式

    // 方式一:中括号形式
    [super viewDidLoad];
    
    // 方式二:系统performSelector形式,相关的几个api,但最多只支持2个参数
    [obj performSelector:@selector(setName:) withObject:@"bfoOnline"];
    
    // 方式三:NSMethodSignature + NSInvocation形式,没有参数限制
    [obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
    

    NSMethodSignature+ selector+ NSInvocation简单说明

    NSMethodSignature:方法签名中保存了方法的名称/参数/返回值,协同NSInvocation来进行消息的转发。方法签名一般是用来设置参数和获取返回值的, 和方法的调用没有太大的关系。

    selector :SEL类型 它简单的唯一标示了一个方法,扮演类似于一个动态函数指针,可以简单理解为一个唯一的id

    NSInvocation:它是命令模式的一种实现,它包含选择器、方法签名、相应的参数以及目标对象。当NSInvocation被调用,它会在运行时通过目标对象去寻找对应的方法,从而确保唯一性。

    方式三 selector+NSMethodSignature + NSInvocation使用

    封装在NSObject分类里的核心代码(具体步骤和使用请看注释)

    // 扩展performSelector方法,支持多参数(系统最多只支持2个参数)
    - (id)performSelector:(SEL)aSel withObjects:(NSArray*)objects {
        id res = nil;
    
        // 1 根据sel实例化NSMethodSignature
        NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSel];
        if (!signature) {
            // 没找到,抛出异常
            @throw [NSException exceptionWithName:@"抛异常错误" reason:@"没有这个方法,或这个方法名字错误" userInfo:nil];
            return res;
        }
        // 2 创建invocation对象
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        // 3 设置调用者
        [invocation setTarget:self];
        // 4 设置selector
        [invocation setSelector:aSel];
        // 5 设置参数
        NSUInteger argsCount = signature.numberOfArguments - 2; // 去掉默认的2个参数:self和_cmd(调用者target和selector)
        NSUInteger arrCount = objects.count;
        // 不能简单的通过遍历参数数组来设置参数,因为外界传进来的参数个数是不可控的,而是取上述两者之间最小值
        NSUInteger resultCount = MIN(argsCount, arrCount);
        for (int i=0; i<resultCount; i++) {
            id obj = objects[i];
            if ([obj isKindOfClass:[NSNull class]]) {
                obj = nil;
            }
            [invocation setArgument:&obj atIndex:i+2];
        }
        // 6 调用
        [invocation invoke];
    
        // 7 判断当前调用方法是否有返回值
        if (signature.methodReturnLength != 0) {
            [invocation getReturnValue:&res];
        }
        return res;
    }
    

    被调用的方法实现(暂时也封装在该分类,在使用过程中 特别是消息转发场景下 注意实现地方)

    // 调用的方法实现 (多参数)
    - (id)run:(NSString*)str1 str2:(NSString*)str2 str3:(NSString*)str3 str4:(NSString*)str4{
        NSLog(@"%@-----%@-----%@-----%@",str1,str2,str3,str4);
        return nil;
    }
    

    vc里简单使用

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSObject *obj = [NSObject new];
        NSArray *arrArgs = @[@"Hello",@"how",@"are",@"you"];
        [obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
    }
    

    运行结果


    fine

    注意

    参数个数计算问题,注意signature.numberOfArguments - 2,传入的参数需要和其保持一致,只能小于其而不能大于它。


    使用二: block+NSInvocation

    api声明部分

    id invokeBlock(id block,NSArray *args);
    
    @interface NSObject (Test)
    @property (nonatomic,copy)NSString *name;
    - (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects;
    - (id)run:(NSString*)str1 str2:(NSString*)str2 str3:(NSString*)str3 str4:(NSString*)str4;
    @end
    

    核心代码(NSObject+Test.m 全局实现部分)

    id invokeBlock(id block,NSArray *args){
        
        if (!block) {
            return  nil;
        }
        id target = [block copy];
        const char *_Block_signature(void*);
        const char *signature = _Block_signature((__bridge void*)target);
        
        NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:signature];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
        invocation.target = target;
        if ([args isKindOfClass:[NSArray class]]) {
            NSInteger count = MIN(args.count, methodSignature.numberOfArguments-1);
            for (int i=0; i<count; i++) {
                const char *type = [methodSignature getArgumentTypeAtIndex:1+i];
                NSString *typeStr = [NSString stringWithUTF8String:type];
                if ([typeStr containsString:@"\""]) {
                    type = [typeStr substringToIndex:1].UTF8String;
                }
                if (strcmp(type, "@")==0) {
                    id argument = args[i];
                    [invocation setArgument:&argument atIndex:1+i];
                }
                
            }
        }
        [invocation invoke];
        
        id returnVal;
        const char *type = methodSignature.methodReturnType;
        NSString *returnType = [NSString stringWithUTF8String:type];
        if ([returnType containsString:@"\""]) {
            type = [returnType substringToIndex:1].UTF8String;
        }
        if (strcmp(type, "@") == 0) {
            [invocation getReturnValue:&returnVal];
        }
        return returnVal;
        
    }
    

    ViewController.m里面调用

    - (void)viewDidLoad {
        [super viewDidLoad];
        
    //    NSObject *obj = [NSObject new];
    //    NSArray *arrArgs = @[@"Hello",@"how",@"are",@"you"];
    //    [obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
        
    //    // 奔溃 :-[CFString release]: message sent to deallocated instance
    //    id returnValue = invokeBlock((id)^(NSString *a,NSString *b){return [NSString stringWithFormat:@"%@ and %@",a,b];}, @[@"01",@"bfo"]);
        self.returnValue = invokeBlock((id)^(NSString *a,NSString *b){return [NSString stringWithFormat:@"%@ and %@",a,b];}, @[@"01",@"bfo"]);
        NSLog(@"%@",self.returnValue);
    }
    

    注意

    注意上面上面注释的局部变量returnValue,若打开注释,执行程序会奔溃,显示EXC_BAD_ACCESS内存访问错误。打开edit scheme里面zombie objects选项(如下图设置),再次运行 console里面会打印-[CFString release]: message sent to deallocated 相关错误,即访问了已被释放的对象


    打开zombie objects选项

    解决:改成如上内存策略属性,运行正常。
    可能原因:自定义的c语言函数,没有被自动释放池包裹,当函数返回值时,就已经被释放。改成copy 属性,引用的时候copy一份,即使之前那个被释放,也不会影响。

    后续:待源码解读支撑~

    相关文章

      网友评论

          本文标题:iOS-performSelector实现多个参数

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