方法调用过程分析流程图
文字描述:
1、从当前类反向遍历继承树,直到NSObject,如果没有找到了,就直接执行,到此调用流程结束,这是正常流程
2、当继承树中没有找到方法实现,进入非正常调用流程(重定向和消息转发)
3、此时可以重写决议方法,在决议方法中动态添加方法实现,一旦添加成功,系统自动跳转执行动态添加的方法实现,到此调用流程结束
4、如果没有重写决议方法或者方法中没有动态添加方法方法实现,首先尝试进入重定向流程(重定向流程标志重定向方法,且返回不是self或者nil)+ (BOOL)resolveInstanceMethod:(SEL)sel
和+ (BOOL)resolveClassMethod:(SEL)sel
。
5、如果重定重定向流程(重定向方法重写的时候也可以动态添加方法实现,只不过不会自动执行,需要手动调用),且将调用重定向到另一个对象,该对象会调用自己的同名方法,流程同上,到此调用流程结束
- (id)forwardingTargetForSelector:(SEL)aSelector
6、如果没有进入重定向流程,就会自动进入到消息转发流程
7、消息转发流程首先必须要执行的是方法签名- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
8、执行完方法签名,最终执行消息转发forwardInvocation:(NSInvocation *)anInvocation
,在此方法中可以将任意修改方法,指定任意对象,执行任意可执行方法
9、如果继承树遍历,动态决议,重定向和消息转发都没有找到合适的方法执行抛出异常unrecognized selector sent to instance
一、动态添加方法实现
注意:
1、动态添加方法实现可以在上述的决议方法(方法返回自动进入执行),重定向方法和转发方法任意位置添加,推荐在决议方法中添加,流程越少越好,减少了代码执行,和未知逻辑判断,相对而言效率可定更高。
2、动态添加方法实现的区别主要在于
class_addMethod([self class], sel, testImp, "v@:");
和
class_addMethod(object_getClass(self), sel, testImp, "v@:");
添加实例方法使用[self class]
,添加类方法使用object_getClass(self)
3、类方法只能通过动态添加方法实现的方式处理,消息相关的都是对象,不是类
//动态添加实例方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"%@",NSStringFromSelector(sel));
id testImpBlock = ^(id self)
{
NSLog(@"对象方法->%@缺少实现",NSStringFromSelector(sel));
};
IMP testImp = imp_implementationWithBlock(testImpBlock);
class_addMethod([self class], sel, testImp, "v@:");
return NO;
}
//动态添加类方法实现
+ (BOOL)resolveClassMethod:(SEL)sel
{
id testImpBlock = ^(id self)
{
NSLog(@"类方法->%@缺少实现",NSStringFromSelector(sel));
};
IMP testImp = imp_implementationWithBlock(testImpBlock);
class_addMethod(object_getClass(self), sel, testImp, "v@:");
return NO;
}
二、重定向
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return [[Student alloc] init];
}
三、消息转发
注意:
1、方法签名和消息转法必须是成对出现的
2、NSInvocation
和performSelector:withObject
OC发送消息的两个方式,NSInvocation
对象中包含了方法执行的对象,要执行的方法及其参数,返回值等信息,NSInvocation
对象直接执行invoke
或者invokeWithTarget:(id)target
就是发送消息,执行方法。eg:如果在重定向中或者方法签名中添加方法实现,在消息转发中,直接用[anInvocation invoke]
就可以完成方法调用,但是不推荐,还是推荐如果使用动态添加方法实现放在决议方法中
//方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
//消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSInteger count = anInvocation.methodSignature.numberOfArguments;
NSLog(@"%@->有%zd个参数",NSStringFromSelector(anInvocation.selector),count);
for (NSInteger i = 0; i < count; i++)
{
if (i > 1)
{
void *arg;
[anInvocation getArgument:&arg atIndex:i];
NSLog(@"%@",(__bridge id)arg);
}
}
// [anInvocation invoke];
}
四、应用 (避免出现unrecognized selector sent to instance异常)
1、直接实现决议方法,动态添加一个空的方法实现,那么永远不会出现次异常,推荐使用,代码如下即可:
//动态添加实例方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"%@",NSStringFromSelector(sel));
id testImpBlock = ^(id self)
{
NSLog(@"对象方法->%@缺少实现",NSStringFromSelector(sel));
};
IMP testImp = imp_implementationWithBlock(testImpBlock);
class_addMethod([self class], sel, testImp, "v@:");
return NO;
}
//动态添加类方法实现
+ (BOOL)resolveClassMethod:(SEL)sel
{
id testImpBlock = ^(id self)
{
NSLog(@"类方法->%@缺少实现",NSStringFromSelector(sel));
};
IMP testImp = imp_implementationWithBlock(testImpBlock);
class_addMethod(object_getClass(self), sel, testImp, "v@:");
return NO;
}
2、对于对象方法,重写消息转发的两个方法:
//方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
//消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
}
3、功能宏
#define NOCRASH \
\
+ (BOOL)resolveInstanceMethod:(SEL)sel\
{\
id testImpBlock = ^(id self)\
{\
NSLog(@"对象方法->%@缺少实现",NSStringFromSelector(sel));\
};\
IMP testImp = imp_implementationWithBlock(testImpBlock);\
class_addMethod([self class], sel, testImp, "v@:");\
return NO;\
}\
\
+ (BOOL)resolveClassMethod:(SEL)sel\
{\
id testImpBlock = ^(id self)\
{\
NSLog(@"类方法->%@缺少实现",NSStringFromSelector(sel));\
};\
IMP testImp = imp_implementationWithBlock(testImpBlock);\
\
class_addMethod(object_getClass(self), sel, testImp, "v@:");\
\
return NO;\
}\
五、Block
1、block类似于函数指针,但是有时内联的(代码直接插入到调用者处,免去了普通函数调用的过程,效率更高)
2、block是一个代码片段,在OC中被看作是OC对象
3、block建议使用copy策略
4、block分为三种__NSGlobalBlock__,__NSMallocBlock__,__NSStackBlock__
,MRC默认情况下是__NSGlobalBlock__
,使用(捕获)了外部非static和全局的变量会变成__NSStackBlock__
,__NSStackBlock__
的block使用了copy
策略,就变成__NSMallocBlock__
的了,ARC情况下copy和strong都一样,系统默认会调用copy变成__NSMallocBlock__
5、在MRC和ARC下都不要使用assign
策略
为什么block使用copy
1、因为MRC下不使用copy的话,如果使用了__NSStackBlock__
的block,可能会出野指针
;
2、因为MRC下不使用copy的话,如果使用了__NSStackBlock__
的block,会出现线程不安全的隐患
;