需求如题所示。。。。。 我们都知道
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
在NSObject中帮selector传递的参数个数最多是 2个 ,当时有时候如果某种需求。。。需要2个以上的参数该如何搞一搞了 ??? 其实网上有很多的例子
#import "NSObject+ArrayObjects.h"
@implementation NSObject (ArrayObjects)
-(id)performSelector:(SEL)aSelector withArray:(NSArray *)objects{
if (aSelector == nil || objects == nil) {
@throw [NSException exceptionWithName:@"NullExcetption" reason: @"aSelector or objects is null" userInfo:nil];
return nil;
}
NSMethodSignature *methodSignature = [[self class] instanceMethodSignatureForSelector:aSelector];
if(methodSignature == nil)
{
@throw [NSException exceptionWithName:@"FunctionNotFoundExcetption" reason: [NSString stringWithFormat:@"the %@ not found",NSStringFromSelector(aSelector)] userInfo:nil];
return nil;
}
else
{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:self];
[invocation setSelector:aSelector];
//签名中方法参数的个数,内部包含了self和_cmd,所以参数从第3个开始
NSInteger signatureParamCount = methodSignature.numberOfArguments - 2;
NSInteger requireParamCount = objects.count;
NSInteger resultParamCount = MIN(signatureParamCount, requireParamCount);
for (NSInteger i = 0; i < resultParamCount; i++) {
id obj = objects[i];
[invocation setArgument:&obj atIndex:i+2];
}
[invocation invoke];
id callBackObject = nil;
if(methodSignature.methodReturnLength)
{
[invocation getReturnValue:&callBackObject];
}
return callBackObject;
}
}
@end
虽然这个一度娘就能度到,而且感觉很牛逼,其实是有bug和缺陷性的,然后对数组里面的数据有要求,我们都知道OC是兼容C代码的。。往里面放入block或结构体就瞎了。。。。。。 我在测试AFNetworking项目的时候发现上面代码存在问题,然后找到了一个比较好一点的解决方案 ,当然github上搞一搞的 ,感觉还可以没报错
#import <Foundation/Foundation.h>
@interface NSObject (VKMsgSend)
+ (id)VKCallSelector:(SEL)selector error:(NSError *__autoreleasing *)error,...;
+ (id)VKCallSelectorName:(NSString *)selName error:(NSError *__autoreleasing *)error,...;
- (id)VKCallSelector:(SEL)selector error:(NSError *__autoreleasing *)error,...;
- (id)VKCallSelectorName:(NSString *)selName error:(NSError *__autoreleasing *)error,...;
@end
@interface NSString (VKMsgSend)
- (id)VKCallClassSelector:(SEL)selector error:(NSError *__autoreleasing *)error,...;
- (id)VKCallClassSelectorName:(NSString *)selName error:(NSError *__autoreleasing *)error,...;
- (id)VKCallClassAllocInitSelector:(SEL)selector error:(NSError *__autoreleasing *)error,...;
- (id)VKCallClassAllocInitSelectorName:(NSString *)selName error:(NSError *__autoreleasing *)error,...;
@end
#import "VKMsgSend.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIApplication.h>
#endif
#pragma mark : vk_nilObject
@interface vk_pointer : NSObject
@property (nonatomic) void *pointer;
@end
@implementation vk_pointer
@end
@interface vk_nilObject : NSObject
@end
@implementation vk_nilObject
@end
#pragma mark : static
static NSLock *_vkMethodSignatureLock;
static NSMutableDictionary *_vkMethodSignatureCache;
static vk_nilObject *vknilPointer = nil;
static NSString *vk_extractStructName(NSString *typeEncodeString){
NSArray *array = [typeEncodeString componentsSeparatedByString:@"="];
NSString *typeString = array[0];
__block int firstVaildIndex = 0;
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
unichar c = [typeEncodeString characterAtIndex:idx];
if (c=='{'||c=='_') {
firstVaildIndex++;
}else{
*stop = YES;
}
}];
return [typeString substringFromIndex:firstVaildIndex];
}
static NSString *vk_selectorName(SEL selector){
const char *selNameCstr = sel_getName(selector);
NSString *selName = [[NSString alloc]initWithUTF8String:selNameCstr];
return selName;
}
static NSMethodSignature *vk_getMethodSignature(Class cls, SEL selector){
if (!_vkMethodSignatureLock) {
_vkMethodSignatureLock = [[NSLock alloc] init];
}
[_vkMethodSignatureLock lock];
if (!_vkMethodSignatureCache) {
_vkMethodSignatureCache = [[NSMutableDictionary alloc]init];
}
if (!_vkMethodSignatureCache[cls]) {
_vkMethodSignatureCache[(id<NSCopying>)cls] =[[NSMutableDictionary alloc]init];
}
NSString *selName = vk_selectorName(selector);
NSMethodSignature *methodSignature = _vkMethodSignatureCache[cls][selName];
if (!methodSignature) {
methodSignature = [cls instanceMethodSignatureForSelector:selector];
if (methodSignature) {
_vkMethodSignatureCache[cls][selName] = methodSignature;
}else
{
methodSignature = [cls methodSignatureForSelector:selector];
if (methodSignature) {
_vkMethodSignatureCache[cls][selName] = methodSignature;
}
}
}
[_vkMethodSignatureLock unlock];
return methodSignature;
}
static void vk_generateError(NSString *errorInfo, NSError **error){
if (error) {
*error = [NSError errorWithDomain:errorInfo code:0 userInfo:nil];
}
}
static id vk_targetCallSelectorWithArgumentError(id target, SEL selector, NSArray *argsArr, NSError *__autoreleasing *error){
Class cls = [target class];
NSMethodSignature *methodSignature = vk_getMethodSignature(cls, selector);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:target];
[invocation setSelector:selector];
NSMutableArray* _markArray;
for (NSUInteger i = 2; i< [methodSignature numberOfArguments]; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
id valObj = argsArr[i-2];
switch (argumentType[0]=='r'?argumentType[1]:argumentType[0]) {
#define VK_CALL_ARG_CASE(_typeString, _type, _selector) \
case _typeString: { \
_type value = [valObj _selector]; \
[invocation setArgument:&value atIndex:i];\
break; \
}
VK_CALL_ARG_CASE('c', char, charValue)
VK_CALL_ARG_CASE('C', unsigned char, unsignedCharValue)
VK_CALL_ARG_CASE('s', short, shortValue)
VK_CALL_ARG_CASE('S', unsigned short, unsignedShortValue)
VK_CALL_ARG_CASE('i', int, intValue)
VK_CALL_ARG_CASE('I', unsigned int, unsignedIntValue)
VK_CALL_ARG_CASE('l', long, longValue)
VK_CALL_ARG_CASE('L', unsigned long, unsignedLongValue)
VK_CALL_ARG_CASE('q', long long, longLongValue)
VK_CALL_ARG_CASE('Q', unsigned long long, unsignedLongLongValue)
VK_CALL_ARG_CASE('f', float, floatValue)
VK_CALL_ARG_CASE('d', double, doubleValue)
VK_CALL_ARG_CASE('B', BOOL, boolValue)
case ':':{
NSString *selName = valObj;
SEL selValue = NSSelectorFromString(selName);
[invocation setArgument:&selValue atIndex:i];
}
break;
case '{':{
NSString *typeString = vk_extractStructName([NSString stringWithUTF8String:argumentType]);
NSValue *val = (NSValue *)valObj;
#define vk_CALL_ARG_STRUCT(_type, _methodName) \
if ([typeString rangeOfString:@#_type].location != NSNotFound) { \
_type value = [val _methodName]; \
[invocation setArgument:&value atIndex:i]; \
break; \
}
vk_CALL_ARG_STRUCT(CGRect, CGRectValue)
vk_CALL_ARG_STRUCT(CGPoint, CGPointValue)
vk_CALL_ARG_STRUCT(CGSize, CGSizeValue)
vk_CALL_ARG_STRUCT(NSRange, rangeValue)
vk_CALL_ARG_STRUCT(CGAffineTransform, CGAffineTransformValue)
vk_CALL_ARG_STRUCT(UIEdgeInsets, UIEdgeInsetsValue)
vk_CALL_ARG_STRUCT(UIOffset, UIOffsetValue)
vk_CALL_ARG_STRUCT(CGVector, CGVectorValue)
}
break;
case '*':{
NSCAssert(NO, @"argument boxing wrong,char* is not supported");
}
break;
case '^':{
vk_pointer *value = valObj;
void *pointer = value.pointer;
id obj = *((__unsafe_unretained id *)pointer);
if (!obj) {
if (argumentType[1] == '@') {
if (!_markArray) {
_markArray = [[NSMutableArray alloc] init];
}
[_markArray addObject:valObj];
}
}
[invocation setArgument:&pointer atIndex:i];
}
break;
case '#':{
[invocation setArgument:&valObj atIndex:i];
}
break;
default:{
if ([valObj isKindOfClass:[vk_nilObject class]]) {
[invocation setArgument:&vknilPointer atIndex:i];
}else{
[invocation setArgument:&valObj atIndex:i];
}
}
}
}
[invocation invoke];
if ([_markArray count] > 0) {
for (vk_pointer *pointerObj in _markArray) {
void *pointer = pointerObj.pointer;
id obj = *((__unsafe_unretained id *)pointer);
if (obj) {
CFRetain((__bridge CFTypeRef)(obj));
}
}
}
const char *returnType = [methodSignature methodReturnType];
NSString *selName = vk_selectorName(selector);
if (strncmp(returnType, "v", 1) != 0 ) {
if (strncmp(returnType, "@", 1) == 0) {
void *result;
[invocation getReturnValue:&result];
if (result == NULL) {
return nil;
}
id returnValue;
if ([selName isEqualToString:@"alloc"] || [selName isEqualToString:@"new"] || [selName isEqualToString:@"copy"] || [selName isEqualToString:@"mutableCopy"]) {
returnValue = (__bridge_transfer id)result;
}else{
returnValue = (__bridge id)result;
}
return returnValue;
} else {
switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
#define vk_CALL_RET_CASE(_typeString, _type) \
case _typeString: { \
_type returnValue; \
[invocation getReturnValue:&returnValue];\
return @(returnValue); \
break; \
}
vk_CALL_RET_CASE('c', char)
vk_CALL_RET_CASE('C', unsigned char)
vk_CALL_RET_CASE('s', short)
vk_CALL_RET_CASE('S', unsigned short)
vk_CALL_RET_CASE('i', int)
vk_CALL_RET_CASE('I', unsigned int)
vk_CALL_RET_CASE('l', long)
vk_CALL_RET_CASE('L', unsigned long)
vk_CALL_RET_CASE('q', long long)
vk_CALL_RET_CASE('Q', unsigned long long)
vk_CALL_RET_CASE('f', float)
vk_CALL_RET_CASE('d', double)
vk_CALL_RET_CASE('B', BOOL)
case '{': {
NSString *typeString = vk_extractStructName([NSString stringWithUTF8String:returnType]);
#define vk_CALL_RET_STRUCT(_type) \
if ([typeString rangeOfString:@#_type].location != NSNotFound) { \
_type result; \
[invocation getReturnValue:&result];\
NSValue * returnValue = [NSValue valueWithBytes:&(result) objCType:@encode(_type)];\
return returnValue;\
}
vk_CALL_RET_STRUCT(CGRect)
vk_CALL_RET_STRUCT(CGPoint)
vk_CALL_RET_STRUCT(CGSize)
vk_CALL_RET_STRUCT(NSRange)
vk_CALL_RET_STRUCT(CGAffineTransform)
vk_CALL_RET_STRUCT(UIEdgeInsets)
vk_CALL_RET_STRUCT(UIOffset)
vk_CALL_RET_STRUCT(CGVector)
}
break;
case '*':{
}
break;
case '^': {
}
break;
case '#': {
}
break;
}
return nil;
}
}
return nil;
};
static NSArray *vk_targetBoxingArguments(va_list argList, Class cls, SEL selector, NSError *__autoreleasing *error){
NSMethodSignature *methodSignature = vk_getMethodSignature(cls, selector);
NSString *selName = vk_selectorName(selector);
if (!methodSignature) {
NSString* errorStr = [NSString stringWithFormat:@"unrecognized selector (%@)", selName];
vk_generateError(errorStr,error);
return nil;
}
NSMutableArray *argumentsBoxingArray = [[NSMutableArray alloc]init];
for (NSUInteger i = 2; i < [methodSignature numberOfArguments]; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
#define vk_BOXING_ARG_CASE(_typeString, _type)\
case _typeString: {\
_type value = va_arg(argList, _type);\
[argumentsBoxingArray addObject:@(value)];\
break; \
}\
vk_BOXING_ARG_CASE('c', int)
vk_BOXING_ARG_CASE('C', int)
vk_BOXING_ARG_CASE('s', int)
vk_BOXING_ARG_CASE('S', int)
vk_BOXING_ARG_CASE('i', int)
vk_BOXING_ARG_CASE('I', unsigned int)
vk_BOXING_ARG_CASE('l', long)
vk_BOXING_ARG_CASE('L', unsigned long)
vk_BOXING_ARG_CASE('q', long long)
vk_BOXING_ARG_CASE('Q', unsigned long long)
vk_BOXING_ARG_CASE('f', double)
vk_BOXING_ARG_CASE('d', double)
vk_BOXING_ARG_CASE('B', int)
case ':': {
SEL value = va_arg(argList, SEL);
NSString *selValueName = NSStringFromSelector(value);
[argumentsBoxingArray addObject:selValueName];
}
break;
case '{': {
NSString *typeString = vk_extractStructName([NSString stringWithUTF8String:argumentType]);
#define vk_FWD_ARG_STRUCT(_type, _methodName) \
if ([typeString rangeOfString:@#_type].location != NSNotFound) { \
_type val = va_arg(argList, _type);\
NSValue* value = [NSValue _methodName:val];\
[argumentsBoxingArray addObject:value]; \
break; \
}
vk_FWD_ARG_STRUCT(CGRect, valueWithCGRect)
vk_FWD_ARG_STRUCT(CGPoint, valueWithCGPoint)
vk_FWD_ARG_STRUCT(CGSize, valueWithCGSize)
vk_FWD_ARG_STRUCT(NSRange, valueWithRange)
vk_FWD_ARG_STRUCT(CGAffineTransform, valueWithCGAffineTransform)
vk_FWD_ARG_STRUCT(UIEdgeInsets, valueWithUIEdgeInsets)
vk_FWD_ARG_STRUCT(UIOffset, valueWithUIOffset)
vk_FWD_ARG_STRUCT(CGVector, valueWithCGVector)
}
break;
case '*':{
vk_generateError(@"unsupported char* argumenst",error);
return nil;
}
break;
case '^': {
void *value = va_arg(argList, void**);
vk_pointer *pointerObj = [[vk_pointer alloc]init];
pointerObj.pointer = value;
[argumentsBoxingArray addObject:pointerObj];
}
break;
case '#': {
Class value = va_arg(argList, Class);
[argumentsBoxingArray addObject:(id)value];
// vk_generateError(@"unsupported class argumenst",error);
// return nil;
}
break;
case '@':{
id value = va_arg(argList, id);
if (value) {
[argumentsBoxingArray addObject:value];
}else{
[argumentsBoxingArray addObject:[vk_nilObject new]];
}
}
break;
default: {
vk_generateError(@"unsupported argumenst",error);
return nil;
}
}
}
return [argumentsBoxingArray copy];
}
@implementation NSObject (VKMsgSend)
+ (id)VKCallSelectorName:(NSString *)selName error:(NSError *__autoreleasing *)error,...{
va_list argList;
va_start(argList, error);
SEL selector = NSSelectorFromString(selName);
NSArray *boxingAruments = vk_targetBoxingArguments(argList, [self class], selector, error);
va_end(argList);
if (!boxingAruments) {
return nil;
}
return vk_targetCallSelectorWithArgumentError(self, selector, boxingAruments, error);
}
+ (id)VKCallSelector:(SEL)selector error:(NSError *__autoreleasing *)error,...{
va_list argList;
va_start(argList, error);
NSArray* boxingArguments = vk_targetBoxingArguments(argList, [self class], selector, error);
va_end(argList);
if (!boxingArguments) {
return nil;
}
return vk_targetCallSelectorWithArgumentError(self, selector, boxingArguments, error);
}
- (id)VKCallSelectorName:(NSString *)selName error:(NSError *__autoreleasing *)error,...{
va_list argList;
va_start(argList, error);
SEL selector = NSSelectorFromString(selName);
NSArray* boxingArguments = vk_targetBoxingArguments(argList, [self class], selector, error);
va_end(argList);
if (!boxingArguments) {
return nil;
}
return vk_targetCallSelectorWithArgumentError(self, selector, boxingArguments, error);
}
- (id)VKCallSelector:(SEL)selector error:(NSError *__autoreleasing *)error,...{
va_list argList;
va_start(argList, error);
NSArray* boxingArguments = vk_targetBoxingArguments(argList, [self class], selector, error);
va_end(argList);
if (!boxingArguments) {
return nil;
}
return vk_targetCallSelectorWithArgumentError(self, selector, boxingArguments, error);
}
@end
@implementation NSString (VKMsgSend)
-(id)VKCallClassSelector:(SEL)selector error:(NSError *__autoreleasing *)error, ...
{
Class cls = NSClassFromString(self);
if (!cls) {
NSString* errorStr = [NSString stringWithFormat:@"unrecognized className (%@)", self];
vk_generateError(errorStr,error);
return nil;
}
va_list argList;
va_start(argList, error);
NSArray* boxingArguments = vk_targetBoxingArguments(argList, cls, selector, error);
va_end(argList);
if (!boxingArguments) {
return nil;
}
return vk_targetCallSelectorWithArgumentError(cls, selector, boxingArguments, error);
}
-(id)VKCallClassSelectorName:(NSString *)selName error:(NSError *__autoreleasing *)error, ...
{
Class cls = NSClassFromString(self);
if (!cls) {
NSString* errorStr = [NSString stringWithFormat:@"unrecognized className (%@)", self];
vk_generateError(errorStr,error);
return nil;
}
SEL selector = NSSelectorFromString(selName);
va_list argList;
va_start(argList, error);
NSArray* boxingArguments = vk_targetBoxingArguments(argList, cls, selector, error);
va_end(argList);
if (!boxingArguments) {
return nil;
}
return vk_targetCallSelectorWithArgumentError(cls, selector, boxingArguments, error);
}
-(id)VKCallClassAllocInitSelector:(SEL)selector error:(NSError *__autoreleasing *)error, ...
{
Class cls = NSClassFromString(self);
if (!cls) {
NSString* errorStr = [NSString stringWithFormat:@"unrecognized className (%@)", self];
vk_generateError(errorStr,error);
return nil;
}
va_list argList;
va_start(argList, error);
NSArray* boxingArguments = vk_targetBoxingArguments(argList, cls, selector, error);
va_end(argList);
if (!boxingArguments) {
return nil;
}
id allocObj = [cls alloc];
return vk_targetCallSelectorWithArgumentError(allocObj, selector, boxingArguments, error);
}
-(id)VKCallClassAllocInitSelectorName:(NSString *)selName error:(NSError *__autoreleasing *)error, ...
{
Class cls = NSClassFromString(self);
if (!cls) {
NSString* errorStr = [NSString stringWithFormat:@"unrecognized className (%@)", self];
vk_generateError(errorStr,error);
return nil;
}
SEL selector = NSSelectorFromString(selName);
va_list argList;
va_start(argList, error);
NSArray* boxingArguments = vk_targetBoxingArguments(argList, cls, selector, error);
va_end(argList);
if (!boxingArguments) {
return nil;
}
id allocObj = [cls alloc];
return vk_targetCallSelectorWithArgumentError(allocObj, selector, boxingArguments, error);
}
@end
至于怎么使用 。。。。。。。。。
#import "ViewController.h"
#import "VKMsgSend.h"
#import <objc/message.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Class cls = NSClassFromString(@"testClassA");
id abc = [[cls alloc]init];
NSError *err;
//this warning is ok Selector is in testClassA
NSString *return1 = [abc VKCallSelector:@selector(testfunction:withB:) error:&err,4,3.5f];
NSLog(@"%@",return1);
NSNumber *return3 = [abc VKCallSelectorName:@"testfunction:withB:withC:" error:nil,4,3.5,@"haha"];
NSInteger tureReturn3 = [return3 integerValue];
NSLog(@"%@",@(tureReturn3));
NSString *return4 = [abc VKCallSelectorName:@"testfunction:withB:withC:withD:" error:nil,4,3.5,nil, CGRectMake(10, 10, 10, 10)];
NSLog(@"%@",return4);
NSError* testerr2;
[abc VKCallSelectorName:@"testFunctionError:" error:nil,&testerr2];
NSLog(@"see more test case in XCTest Target");
NSLog(@"vk_msgSend_projTests");
//这是一段展示 performselector 缺点和不足的代码,有注释中文解释
[self performShow];
//这是一段展示 objc_msgsend 缺点和不足的代码,有注释和中文解释
[self msgsendShow];
//这是对比展示 VKMsgSend的代码
[self vkshow];
}
-(void)performShow
{
Class cls = NSClassFromString(@"testClassA");
id abc = [[cls alloc]init];
//-(NSString*)testfunction:(int)num withB:(float)boolv
NSString * result = [abc performSelector:@selector(testfunction:withB:) withObject:@4 withObject:@3.5];
NSLog(@"%@",result);
//并且只支持id,如果你敢把基础数值类型封装成number传进去,数值还是错乱的
//这样代码跑进去 int 传了个NSNumber进去 函数内指针全乱,参数值都飞了
//3个参数就不支持了,打开注释你会发现,就没有传3个参数的方法
// [abc performSelector:@selector(testfunction:withB:withC:) withObject:@4.5 withObject:@3 withObject:@"ssss"];
}
-(void)msgsendShow
{
Class cls = NSClassFromString(@"testClassA");
id abc = [[cls alloc]init];
// NSString *result = objc_msgSend(abc, @selector(testfunction:withB:), 4, 3.5);
//很抱歉上面这样的方法,看着用的很方便,但是在iOS 64位下会直接崩溃,xcode8下是直接无法编译
NSString *result2 = ((NSString* (*)(id, SEL, int,float))objc_msgSend)(abc, @selector(testfunction:withB:), 4, 3.5);
//看到没必须这么费劲的写一坨C语言的函数指针强转才可以
NSLog(@"%@",result2);
}
-(void)vkshow
{
//理想状态下 旧的 objc_msgSend就已经很方便了,但是已经不能这么用了,那我就封装出了一个runtime工具
Class cls = NSClassFromString(@"testClassA");
id abc = [[cls alloc]init];
NSError * error;
//很方便吧
[abc VKCallSelectorName:@"testfunction:withB:" error:&error,4,3.5];
//支持所有基础类型,结构体,id类型,class类型,selector类型,block类型,还有指针类型 **
//如果是使用类方法,还可以直接通过类名NSString
[@"testClassA" VKCallClassSelectorName:@"testfunction:withB:withH" error:&error,4,3.5,@"aaa"];
//如果是实例方法,可以直接通过类名NSString,调用init selector,哪怕initWithXX:XX:等自定义的初始化函数都可以
id abcc = [@"testClassA" VKCallClassAllocInitSelectorName:@"init" error:nil];
NSLog(@"%@",abcc);
//省去了手写NSClassFromString 的事情
}
当然,这里附上github地址 https://github.com/Awhisper/VKMsgSend