OC-RunTime: 消息转发之实例方法的转发流程[实例讲解]
OC-RunTime: 消息转发之实例方法的转发流程 分享了消息转发的流程, 本次结合实际例子继续分析一下消息转发流程.
发送不存在的消息
在 ViewController 的 viewDidLoad 中运行 veryTestMethod
方法.
ViewController.m
1 2 3 4 5 6 7 8 9 10 11 12 13
| static NSString * const sPerformInstanceMethodName = @"veryTestMethod";
- (void)viewDidLoad { [super viewDidLoad]; SEL selector = NSSelectorFromString(sPerformInstanceMethodName); SuppressPerformSelectorLeakWarning( [self performSelector:selector withObject:nil]; ); }
|
其中, SuppressPerformSelectorLeakWarning
是定义的一个宏.
1 2 3 4 5 6 7
| #define SuppressPerformSelectorLeakWarning(Stuff) \ do { \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \ Stuff; \ _Pragma("clang diagnostic pop") \ } while (0)
|
在 ViewController 中我并没有写 veryTestMethod
这个函数, 只是借助 performSelector
动态执行, 如果编译运行直接会 crash.
可以查看 NSObject.mm
源码, 里面关于消息转发的几个重要函数都写着 _objc_fatal
, 可谓是招招毙命.
紧接着, 我们可以借助 resolveInstanceMethod
来完成消息转发给 ViewController.
resolveInstanceMethod 转发
重写 NSObject 中的 resolveInstanceMethod
函数.
+resolveInstanceMethod
ViewController.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #import <objc/runtime.h>
+ (BOOL)resolveInstanceMethod:(SEL)sel { NSLog(@"---veryitman--- 1--- +resolveInstanceMethod"); NSString *methodName = NSStringFromSelector(sel); if ([sPerformInstanceMethodName isEqualToString:methodName]) { SEL proxySelector = NSSelectorFromString(@"proxyMethod"); IMP impletor = class_getMethodImplementation(self, proxySelector); Method method = class_getInstanceMethod(self, proxySelector); const char *types = method_getTypeEncoding(method); class_addMethod([self class], sel, impletor, types); return YES; } return [super resolveInstanceMethod:sel]; }
|
ViewController 中实现的 proxyMethod
1 2 3 4 5
| - (void)proxyMethod { NSLog(@"---veryitman--- -proxyMethod of instance's method for OC."); }
|
在 resolveInstanceMethod
中动态添加了 veryTestMethod
方法, 并让 proxyMethod
函数来实现(IMP).
运行可以看到, 程序并没有 crash, 成功的执行了 proxyMethod
.
1 2
| ---veryitman--- 1--- +resolveInstanceMethod ---veryitman--- -proxyMethod of instance's method for OC.
|
到此为止, 我们已经看到动态添加一个方法的实现并成功运行的完整例子.
接下来, 我们将转发给其他对象 MZTempObj
来执行.
自定义被转发的对象
MZTempObj.m
1 2 3 4 5 6
| @implementation MZTempObj
- (void)veryTestMethod { NSLog(@"---veryitman--- veryTestMethod"); }
|
veryTestMethod
就是我们要转发对应的消息.
消息转发实践
接下来我们把向 ViewController 发送 veryTestMethod
的消息转发给 MZTempObj
的 veryTestMethod
方法.
继续重写下面函数, 不过 resolveInstanceMethod
要稍微改造一下, 才能达到我们实践的目的.
- +resolveInstanceMethod
- -forwardingTargetForSelector
- -methodSignatureForSelector
- -forwardInvocation
- -doesNotRecognizeSelector:
ViewController.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #import <objc/runtime.h>
+ (BOOL)resolveInstanceMethod:(SEL)sel { NSLog(@"---veryitman--- 1--- +resolveInstanceMethod: %@", NSStringFromSelector(sel)); NSString *methodName = NSStringFromSelector(sel); if ([@"" isEqualToString:methodName]) { SEL proxySelector = NSSelectorFromString(@"proxyMethod"); IMP impletor = class_getMethodImplementation(self, proxySelector); Method method = class_getInstanceMethod(self, proxySelector); const char *types = method_getTypeEncoding(method); class_addMethod([self class], sel, impletor, types); return YES; } return [super resolveInstanceMethod:sel]; }
- (id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"---veryitman--- 2--- -forwardingTargetForSelector"); NSString *selectorName = NSStringFromSelector(aSelector); if ([sPerformInstanceMethodName isEqualToString:selectorName]) { MZTempObj *myobject = [[MZTempObj alloc] init]; return myobject; } id obj = [super forwardingTargetForSelector:aSelector]; return obj; }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSLog(@"---veryitman--- 3--- -methodSignatureForSelector"); NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; if (nil == signature) { if ([MZTempObj instancesRespondToSelector:aSelector]) { signature = [MZTempObj instanceMethodSignatureForSelector:aSelector]; } } return signature; }
- (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"---veryitman--- 4--- -forwardInvocation"); if ([MZTempObj instancesRespondToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:[[MZTempObj alloc] init]]; } else { [super forwardInvocation:anInvocation]; } }
- (void)doesNotRecognizeSelector:(SEL)aSelector { NSLog(@"---veryitman--- 5--- -doesNotRecognizeSelector: %@", NSStringFromSelector(aSelector)); }
|
运行程序, 控制台打印结果如下:
1 2 3
| ---veryitman--- 1--- +resolveInstanceMethod ---veryitman--- 2--- -forwardingTargetForSelector ---veryitman--- veryTestMethod
|
这里对照之前的流程图是完全符合的, 那么怎么让其执行 3 和 4 呢? 很简单, 修改一下 forwardingTargetForSelector
里面的实现即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| - (id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"---veryitman--- 2--- -forwardingTargetForSelector"); NSString *selectorName = NSStringFromSelector(aSelector); if ([@"" isEqualToString:selectorName]) { MZTempObj *myobject = [[MZTempObj alloc] init]; return myobject; } id obj = [super forwardingTargetForSelector:aSelector]; return obj; }
|
再次执行看结果:
1 2 3 4 5 6
| ---veryitman--- 1--- +resolveInstanceMethod veryTestMethod ---veryitman--- 2--- -forwardingTargetForSelector ---veryitman--- 3--- -methodSignatureForSelector ---veryitman--- 1--- +resolveInstanceMethod: _forwardStackInvocation: ---veryitman--- 4--- -forwardInvocation ---veryitman--- veryTestMethod
|
注意: 这里在 3后面会多了一个 1--- resolveInstanceMethod
的打印, 是系统调用的, 此时对应的 sel 是 _forwardStackInvocation
.
如果不去重写 methodSignatureForSelector
打印结果如下:
1 2 3
| ---veryitman--- 1--- +resolveInstanceMethod ---veryitman--- 2--- -forwardingTargetForSelector ---veryitman--- 5--- -doesNotRecognizeSelector: veryTestMethod
|
参考文档
1.Apple RunTime 源码 objc4-723.tar.gz
2.Message Forwarding
推荐
OC-RunTime: 消息转发之实例方法的转发流程
OC-RunTime: 消息转发之类方法的转发流程
OC-RunTime: 总结消息转发中用到的知识点
点击下载文中完整的 Demo.
扫码关注,你我就各多一个朋友~