performSelector 方法的自动俘获特性

来源:互联网 发布:最新电视直播软件 编辑:程序博客网 时间:2024/06/08 11:49

局部变量自动俘获

偶然在调试中发现,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];}
0 0