当一个对象接受一个消息后 系统会去分发表里面查看是否能接受这个消息 如果不行 则会执行下面其中一个方案 执行了其中一个其他的就不会执行了 都要自己去实现
//方案一:
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//方案二:
- (id)forwardingTargetForSelector:(SEL)aSelector;
//方案三:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
动态的添加一个方法 替换那个找不到的方法
但是这个方法是没办法直接调用的 需要用到performSelector
因为performSelector是运行时系统负责去找方法的,在编译时候不做任何校验;如果直接调用编译是会自动校验。 然后class_addMethod添加方法是在运行时添加的,你在编译的时候还没有这个本类方法,所以当然不行啦。
#import
可以动态添加函数 实现@dynamic属性
id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);
+(BOOL)resolveInstanceMethod:(SEL)sel //sel是引起异常的那个函数
{
NSString *selectorString = NSStringFromSelector(sel);//获取sel方法的名字 转换为string
IMP impSet = class_getMethodImplementation(self, @selector(autoDictionarySetter));
if ([selectorString hasPrefix:@"set"])//如果名字里面有set 则动态添加autoDictionarySetter
class_addMethod(self, selector, impSet, "v@:@");//OC的写法添加imp
else//如果不带则添加autoDictionaryGetter
class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");//C的方法添加imp
return YES;
}
return [super resolveInstanceMethod:sel];
}
指向方法实现的指针 每一个方法都有一个对应的IMP
IMP imp = class_getMethodImplementation(self, @selector(funA));
直接强行转换 (IMP)funA
也就是
class_addMethod([self class], sel, (IMP)funA, "v@:");
参数一: Class:就是需要一个类类型 比如[self class]
参数二:传一个方法 这里就是传那个找不到的引起异常的那个方法
参数三:IMP
参数四: const char *types:
”v@:”意思就是这已是一个void类型的方法,没有参数传入。
“i@:”就是说这是一个int类型的方法,没有参数传入。
”i@😡”就是说这是一个int类型的方法,有一个对象参数传入。
因为每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,该参数就是用来描述这个方法的返回值、参数的,v 代表返回值为void,@ 表示self,: 表示_cmd。
test就是那个找不到的那个函数
TestObect.m
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [ForwardTestObect new];
}
return [super forwardingTargetForSelector:aSelector];
}
ForwardTestObect.m
- (void)test{
NSLog(@"%@ --- test ---",self);
}
当对象接收到消息后发现没有实现该消息的方法 则他会先调用
methodSignatureForSelector
//这个函数用来生成方法签名 签名是给forwardInvocation参数NSInvocation调用
再调用
-(void)forwardInvocation:(NSInvocation*)anInvocation;
//这个方法是属于NSObject类
然后这个方法会调用
-(void)doesNotRecognizeSelector:(SEL)aSelector;/
/用于生产错误信息 表示异常NSInvalidArgumentException发生 无法处理参数选择器对应的消息
所以我们只需要重定义这个方法就行了
forwardInvocation需要传入NSInvocation的对象 这个对象存储了目标,选择器,参数等消息转发所需要的全部元素
NSInvocation的头文件 Foundation/NSInvocation.h
对象中存储了目标,选择器,参数等消息转发所需要的全部元素。
向目标参数对象发送表示接受者的消息 将消息的结果返回给源发送着
NSMethodSignature类 对象保存着方法的参数和返回值
为了使运行时系统能够使用传送目的地的对象信息生成NSInvocation实例,所以需要重新定义返回的方法签名对象
但是受用转送方法处理的消息不能被respondsToSelector:等方法。当需要知道该消息是否位目标对象可处理的消息,必须重新定义respondsToSelector:等方法
@interface B:NSObject
-(void)funcB;
@end
@implementation B
-(void)funcB
{
NSLog(@"%s",__func__);
}
@end
@interface A:NSObject
@property B* b;//作为A的属性 就是打算调用B的方法
-(void)funcA;
@end
@implementation A
-(id) init
{
self = [super init];
if(self!=nil)
{
_b = [[B alloc]init];
}
return self;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = [anInvocation selector];//获取无法调用的这个消息
if([_b respondsToSelector:sel])//询问b是否可以调用 如果可以则向目标参数对象发送这个消息 然后将结果返回回来
{
NSLog(@"转给b去调用了");
[anInvocation invokeWithTarget:_b];
}
else//如果不能则看父类是否可以
{
NSLog(@"b不能调用 转给父类去看看");
[super forwardInvocation:anInvocation];//这个是不是没有必要写 因为父类有的 子类一定有
}
}
-(void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"A:%s",__func__);
}
//方法签名重写
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
if([super respondsToSelector:aSelector])//询问b是否可以调用 如果可以则向目标参数对象发送这个消息 然后将结果返回回来
{
return [super methodSignatureForSelector:(SEL)aSelector];
}
return [_b methodSignatureForSelector:aSelector];
}
@end
int main()
{
id a = [[A alloc]init];
[a funcB];
return 0;
}
先查找了自己的分发表 发现没有
则调用methodSignatureForSelector 通过respondsToSelector:aSelector发现这个消息通过父类递归也没有
则forwardInvocation 判断_b这个属性是否可以接受这个无法调用的消息 发现可以 则把这个消息通过invokeWithTarget:_b传给_b接受 然后将结果返回 最后调用doesNotRecognizeSelector报一个异常