iOS performSelector使用int等基本数据类型的问题

NSObject performSelecor方法允许动态方法调用,它支持的参数只能是id类型,不能是基本数据类型。需要传递基本数据类型时,要使用NSInvocation。

iOS Method Swizzling使用陷阱一样,还是以我在项目中遇到的代码片段来说明问题:

NSNumber *appID = @(100);
[EaseapiCom performSelector:NSSelectorFromString(@"initSDK:") withObject:appID];

@implementation EaseapiCom
+ (void)initSDK:(NSInteger)appID {
    //code
}
@end

上面的代码有表达了两层意思:

  • 某些情况下,只能通过反射进行方法调用;
  • 被调用的方法有基本数据类型的参数;

由于NSObject的performSelector的参数必须是一个id类型。所以,开发者尝试将参数先包装成NSNumber,再使用performSelector,希望默认在initSDK:调用时会自动进行NSNumber到NSInteger的转换。

这个问题稍微有点开发经验的同学就能看明白,在initSDK:传入的appID将会是NSNumber的地址,不能自动的从NSNumber转换到NSInteger。也就是本文的第一点内容:performSelector方法不能传递NSInteger等基本数据类型。

将这个问题提给相关同学后,该同学回复说:为什么在有些情况下使用performSelector传值NSInteger却是正常的呢?

该同学没给出具体的正常的case,但我猜测,应该是和NSNumber的内存优化有关。简单说来,在64位系统中,指针是8字节大小。要存储一个NSNumber类型,需要一个8字节的指针和一块存储NSNumber实例的内存。但对于NSNumber、NSDate这样的类型,它的数值一般很小,为了节省内存,苹果引入了Tagged Pointer的机制:对于小类型的NSNumber、NSDate对象,使用Tagged Pointer来存储,Tagged Pointer即包含有地址信息,也包含有值信息,有固定的格式要求。有可能存在Tagged Pointer的值和实际的NSNumber的值时一致的情况(仅猜测)。

但Tagged Pointer并不能成为performSelector可以使用基本数据类型的理由,无论有没有Tagged Pointer机制,在performSelector中使用基本数据类型都是错误的。

如何通过反射传入基本数据类型参数?

performSelector的方式肯定是走不通的,可以试试使用NSInvocation。

NSInteger appid = 100;
Class class = NSClassFromString(@"EaseapiCom");
SEL sel = NSSelectorFromString(@"initSDK:");
NSMethodSignature *signature = [class methodSignatureForSelector:sel];

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&appid atIndex:2];
invocation.selector = sel;
invocation.target = class;
[invocation invoke];

正文完结。后续本博客发布的文章,都将在代码片段或正文中携带easeapi标识,恶意采集的问题越来越严重了。

其它文章

iOS Method Swizzling使用陷阱
CocoaPods Podfile and podspec configurations
NSUserDefaults的suitename
iOS TestFlight的局限性及改进措施