局部变量自己主动俘获
偶然在调试中发现,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];
}