OC中的方法调用,其实都是转换为objc_megSend
函数调用。
objc_megSend的执行流程可以分为3大阶段
- 消息发送,如果消息发送成功,则调用相关方法。如果失败进入下一阶段。
- 动态方法解析,如果成功,则调用相关方法。如果失败进入下一阶段。
- 消息转发,如果失败则会"报找不到方法错误"
消息发送流程
动态方法解析
流程
动态添加方法
当消息发送进入动态方法解析阶段,调用+resolveInstanceMethod:
或+resolveClassMethod:
方法时,可以在此时动态的添加方法。
有两种动态添加方法的方式。
-
动态其他OC方法
@interface GLPerson : NSObject - (void) abc; @end #import <objc/runtime.h> @implementation GLPerson - (void) other { NSLog(@"%@",NSStringFromSelector(_cmd)); NSLog(@"%s",__func__); } + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(abc)) { //将other方法的实现添加。Method可以理解为等价于struct method_t * Method method = class_getInstanceMethod(self, @selector(other)); class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method)); return YES; } return [super resolveInstanceMethod:sel]; } @end //// ===== main int main(int argc, const char * argv[]) { @autoreleasepool { GLPerson *person = [[GLPerson alloc] init]; [person abc]; } return 0; } //程序输出: //abc /-[GLPerson other]
-
动态添加C语言函数
@interface GLPerson : NSObject - (void) abc; @end #import <objc/runtime.h> @implementation GLPerson - (void) other { NSLog(@"%@",NSStringFromSelector(_cmd)); NSLog(@"%s",__func__); } + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(abc)) { class_addMethod(self, sel, /* C语言的函数名就是函数地址 */ (IMP)other, "v16@0:8"); return YES; } return [super resolveInstanceMethod:sel]; } @end //// ===== main int main(int argc, const char * argv[]) { @autoreleasepool { GLPerson *person = [[GLPerson alloc] init]; [person abc]; } return 0; } //程序输出: //abc //self=<GLPerson: 0x10301a950>
消息转发流程
消息转发,顾名思义就是将消息转发给别的对象。
当方法调用来到消息转发阶段:
-
会先调用
-forwardingTargetForSelector:
或者+forwardingTargetForSelector:
方法。如果返回值不为
nil
,则objc_msgSend(返回值,sel)
。如果返回值为
nil
或者没有实现,则进入第二个步骤-
实例对象调用
-forwardingTargetForSelector:
,类对象调用+forwardingTargetForSelector:
-
如果返回值为
nil
,则会调用-methodSignatureForSelector:
或者+methodSignatureForSelector:
,该方法要求返回一个方法签名。如果返回
nil
,则会调用doesNotRecognizeSelector:
抛出异常错误。-
如果返回值不为
nil
,则进入下一步骤
-
methodSignatureForSelector:
返回值不为nil
,则调用forwardInvocation:
,这个方法中可以进行任意操作。并且也不会导致崩溃。