performSelector 方法的自己主动俘获特性

局部变量自己主动俘获

偶然在调试中发现,performSelector 方法具有自己主动俘获变量的特性。试看例如以下代码:

CGFloat c = _addViewShowing ? 0 : 80;
    if([self respondsToSelector:@selector(jsq_setToolbarBottomLayoutGuideConstant:)]){
        [self performSelector:@selector(jsq_setToolbarBottomLayoutGuideConstant:) withObject:nil];
        ...
    }

这里请注意 [self performSelector:@selector(jsq_setToolbarBottomLayoutGuideConstant:) withObject:nil]; 一句。

在调用 performSelector 方法时,会自己主动把变量 CGFloat c 俘获到 jsq_setToolbarBottomLayoutGuideConstant: 方法调用中去。也就是说,相当于向该方法传递了參数 c。

值得注意的是。变量 c 的类型必须和 jsq_setToolbarBottomLayoutGuideConstant: 方法參数的类型同样。否则不会自己主动俘获。比如, c 变量为 CGFloat,而方法jsq_setToolbarBottomLayoutGuideConstant: 的參数同样也为 CGFloat:

- (void)jsq_setToolbarBottomLayoutGuideConstant:(CGFloat)constant

假设你将 c 的类型改成 int。则 c 不会被自己主动俘获。

利用这个特性,我们能够在 performSelector 调用时,自己主动传递变量给目标方法,而不用通过 withObject 来传參。

自己主动俘获方法參数

假设我们将上述代码定义为一个方法:

-(void)setToolbarSpaceToBottom:(CGFloat)constant{
    // 调用私有方法 jsq_setToolbarBottomLayoutGuideConstant
    CGFloat c = _addViewShowing ? 0 : 80;
    if([self respondsToSelector:@selector(jsq_setToolbarBottomLayoutGuideConstant:)]){
        [self performSelector:@selector(jsq_setToolbarBottomLayoutGuideConstant:) withObject:nil];
    }
}

则变量 c 能够省略,由于 performSelector 会自己主动俘获方法參数 constant。将之传递给 jsq_setToolbarBottomLayoutGuideConstant: 调用。于是这种方法能够写成:

-(void)setToolbarSpaceToBottom:(CGFloat)constant{
    if([self respondsToSelector:@selector(jsq_setToolbarBottomLayoutGuideConstant:)]){
        [self performSelector:@selector(jsq_setToolbarBottomLayoutGuideConstant:) withObject:nil];
    }
}

自己主动俘获的代价

上述代码同一时候会带来一个负面作用。即在两个 @selector 引用的地方出现两个同样编译警告:

Undeclared selector ‘jsq_setToolbarBottomLayoutGuideConstant:’

这是由于 jsq_setToolbarBottomLayoutGuideConstant: 方法来自于父类。它是私有的(没有将方法进行静态声明——即未在头文件里声明)。

对一切未静态声明的方法进行 performSelector 时。编译器都会提示 Undeclared selector。

你能够用以下的技术消除它们。但这会导致自己主动俘获失效。

不能使用自己主动俘获的情况

要消灭编译警告,我们能够使用 NSSelectorFromString 来引用 selector。
比如:

CGFloat c = constant;
    SEL sel=NSSelectorFromString(@"jsq_setToolbarBottomLayoutGuideConstant:");
    if([self respondsToSelector:sel]){
        // 忽略编译器警告
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:sel withObject:nil];
        #pragma clang diagnostic pop
    }

但这样的情况下。变量 c 不会被自己主动俘获。假设你在 jsq_setToolbarBottomLayoutGuideConstant: 方法的第一行代码加上断点执行程序。当程序执行到断点处时,打印參数的值,你会发现其值为 NaN 。假设继续执行代码。App 会崩溃。

这样的情况下。我们无法使用自己主动俘获,因此仅仅能使用 withObject 来传递參数了。

但由于 CGFloat 不是 NSObject,无法用 [performSelector: withObjectd:] 来传參。因此要使用 NSInvocation 来调用:

-(void)setToolbarSpaceToBottom:(CGFloat)constant{

    SEL sel=NSSelectorFromString(@"jsq_setToolbarBottomLayoutGuideConstant:");

    NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:sel]];

    [invoc setSelector:sel];
    [invoc setTarget:self];

    [invoc setArgument:&constant atIndex:2];//"Indices 0 and 1 indicate the hidden arguments self and _cmd"

    [invoc performSelector:@selector(invoke) withObject:nil];
}
原文地址:https://www.cnblogs.com/jhcelue/p/7255714.html