其实在日常iOS开发中,真正使用invocation的地方还是很少,趁着这段时间公司项目空闲期,对之前的面试题做个简单回忆总结。
怎么实现performSelector支持2个以上的参数?
说说对isa指针的理解?
常见的crash?说说unrecognize selector to instance 具体发生过程
以上,其实都涉及到方法具体是如何调用执行的。以下都是对问题一做个简单说明使用
使用一:实现performSelector传入>2参数
OC里常用调用方法的几种方式
// 方式一:中括号形式
[super viewDidLoad];
// 方式二:系统performSelector形式,相关的几个api,但最多只支持2个参数
[obj performSelector:@selector(setName:) withObject:@"bfoOnline"];
// 方式三:NSMethodSignature + NSInvocation形式,没有参数限制
[obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
NSMethodSignature+ selector+ NSInvocation简单说明
NSMethodSignature:方法签名中保存了方法的名称/参数/返回值,协同NSInvocation来进行消息的转发。方法签名一般是用来设置参数和获取返回值的, 和方法的调用没有太大的关系。
selector :SEL类型 它简单的唯一标示了一个方法,扮演类似于一个动态函数指针,可以简单理解为一个唯一的id
NSInvocation:它是命令模式的一种实现,它包含选择器、方法签名、相应的参数以及目标对象。当NSInvocation被调用,它会在运行时通过目标对象去寻找对应的方法,从而确保唯一性。
方式三 selector+NSMethodSignature + NSInvocation使用
封装在NSObject分类里的核心代码(具体步骤和使用请看注释)
// 扩展performSelector方法,支持多参数(系统最多只支持2个参数)
- (id)performSelector:(SEL)aSel withObjects:(NSArray*)objects {
id res = nil;
// 1 根据sel实例化NSMethodSignature
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSel];
if (!signature) {
// 没找到,抛出异常
@throw [NSException exceptionWithName:@"抛异常错误" reason:@"没有这个方法,或这个方法名字错误" userInfo:nil];
return res;
}
// 2 创建invocation对象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
// 3 设置调用者
[invocation setTarget:self];
// 4 设置selector
[invocation setSelector:aSel];
// 5 设置参数
NSUInteger argsCount = signature.numberOfArguments - 2; // 去掉默认的2个参数:self和_cmd(调用者target和selector)
NSUInteger arrCount = objects.count;
// 不能简单的通过遍历参数数组来设置参数,因为外界传进来的参数个数是不可控的,而是取上述两者之间最小值
NSUInteger resultCount = MIN(argsCount, arrCount);
for (int i=0; i<resultCount; i++) {
id obj = objects[i];
if ([obj isKindOfClass:[NSNull class]]) {
obj = nil;
}
[invocation setArgument:&obj atIndex:i+2];
}
// 6 调用
[invocation invoke];
// 7 判断当前调用方法是否有返回值
if (signature.methodReturnLength != 0) {
[invocation getReturnValue:&res];
}
return res;
}
被调用的方法实现(暂时也封装在该分类,在使用过程中 特别是消息转发场景下 注意实现地方)
// 调用的方法实现 (多参数)
- (id)run:(NSString*)str1 str2:(NSString*)str2 str3:(NSString*)str3 str4:(NSString*)str4{
NSLog(@"%@-----%@-----%@-----%@",str1,str2,str3,str4);
return nil;
}
vc里简单使用
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj = [NSObject new];
NSArray *arrArgs = @[@"Hello",@"how",@"are",@"you"];
[obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
}
运行结果
fine
注意
参数个数计算问题,注意signature.numberOfArguments - 2,传入的参数需要和其保持一致,只能小于其而不能大于它。
使用二: block+NSInvocation
api声明部分
id invokeBlock(id block,NSArray *args);
@interface NSObject (Test)
@property (nonatomic,copy)NSString *name;
- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects;
- (id)run:(NSString*)str1 str2:(NSString*)str2 str3:(NSString*)str3 str4:(NSString*)str4;
@end
核心代码(NSObject+Test.m 全局实现部分)
id invokeBlock(id block,NSArray *args){
if (!block) {
return nil;
}
id target = [block copy];
const char *_Block_signature(void*);
const char *signature = _Block_signature((__bridge void*)target);
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:signature];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.target = target;
if ([args isKindOfClass:[NSArray class]]) {
NSInteger count = MIN(args.count, methodSignature.numberOfArguments-1);
for (int i=0; i<count; i++) {
const char *type = [methodSignature getArgumentTypeAtIndex:1+i];
NSString *typeStr = [NSString stringWithUTF8String:type];
if ([typeStr containsString:@"\""]) {
type = [typeStr substringToIndex:1].UTF8String;
}
if (strcmp(type, "@")==0) {
id argument = args[i];
[invocation setArgument:&argument atIndex:1+i];
}
}
}
[invocation invoke];
id returnVal;
const char *type = methodSignature.methodReturnType;
NSString *returnType = [NSString stringWithUTF8String:type];
if ([returnType containsString:@"\""]) {
type = [returnType substringToIndex:1].UTF8String;
}
if (strcmp(type, "@") == 0) {
[invocation getReturnValue:&returnVal];
}
return returnVal;
}
ViewController.m里面调用
- (void)viewDidLoad {
[super viewDidLoad];
// NSObject *obj = [NSObject new];
// NSArray *arrArgs = @[@"Hello",@"how",@"are",@"you"];
// [obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
// // 奔溃 :-[CFString release]: message sent to deallocated instance
// id returnValue = invokeBlock((id)^(NSString *a,NSString *b){return [NSString stringWithFormat:@"%@ and %@",a,b];}, @[@"01",@"bfo"]);
self.returnValue = invokeBlock((id)^(NSString *a,NSString *b){return [NSString stringWithFormat:@"%@ and %@",a,b];}, @[@"01",@"bfo"]);
NSLog(@"%@",self.returnValue);
}
注意
注意上面上面注释的局部变量returnValue,若打开注释,执行程序会奔溃,显示EXC_BAD_ACCESS内存访问错误。打开edit scheme里面zombie objects选项(如下图设置),再次运行 console里面会打印-[CFString release]: message sent to deallocated 相关错误,即访问了已被释放的对象
打开zombie objects选项
解决:改成如上内存策略属性,运行正常。
可能原因:自定义的c语言函数,没有被自动释放池包裹,当函数返回值时,就已经被释放。改成copy 属性,引用的时候copy一份,即使之前那个被释放,也不会影响。
后续:待源码解读支撑~
网友评论