发送消息时会在对应的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:
方法报unrecognized selector
错。
消息转发到底是什么呢?接下来将会逐一介绍最后的三次机会。
1、动态方法解析
2、备用接收者
3、完整消息转发
动态方法解析
首先,Objective-C运行时会调用+resolveInstanceMethod:
或者+resolveClassMethod:
,让你有机会提供一个函数实现。
如果你添加了函数并返回YES
, 那运行时系统就会重新启动一次消息发送的过程。
备用接收者
如果目标对象实现了-forwardingTargetForSelector:
,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。
完整消息转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
首先它会发送-methodSignatureForSelector:
消息获得函数的参数和返回值类型。
如果-methodSignatureForSelector:
返回nil
,Runtime则会发出-doesNotRecognizeSelector:
消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation
对象并发送-forwardInvocation:
消息给目标对象。
签名参数"v@:"
示例:执行某个没有实现的方法时,会抛出异常并造成crash,但使用runtime运行时特性进行动态方法解析,或使用备用接收者,或进行完整的消息转发后,则可以很好的避免crash问题。
crash时的源码
#import "ViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = @"runtime";
// 执行work函数,但work函数没有实现
[self performSelector:@selector(work:)];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
执行结果,抛出异常,造成crash
2018-07-05 18:41:18.160643+0800 DemoRuntime[9374:632785] -[ViewController work:]: unrecognized selector sent to instance 0x7f917a610f50
2018-07-05 18:41:18.209071+0800 DemoRuntime[9374:632785] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController work:]: unrecognized selector sent to instance 0x7f917a610f50'
*** First throw call stack:
(
0 CoreFoundation 0x000000010a1d21e6 __exceptionPreprocess + 294
1 libobjc.A.dylib 0x0000000109867031 objc_exception_throw + 48
2 CoreFoundation 0x000000010a253784 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 UIKit 0x000000010a87d6db -[UIResponder doesNotRecognizeSelector:] + 295
4 CoreFoundation 0x000000010a154898 ___forwarding___ + 1432
5 CoreFoundation 0x000000010a154278 _CF_forwarding_prep_0 + 120
6 DemoRuntime 0x0000000108f63745 -[ViewController viewDidLoad] + 277
7 UIKit 0x000000010a7f3131 -[UIViewController loadViewIfRequired] + 1215
8 UIKit 0x000000010a83a20c -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 68
9 UIKit 0x000000010a83a4ea -[UINavigationController _startTransition:fromViewController:toViewController:] + 136
10 UIKit 0x000000010a83b61e -[UINavigationController _startDeferredTransitionIfNeeded:] + 870
11 UIKit 0x000000010a83c86c -[UINavigationController __viewWillLayoutSubviews] + 150
12 UIKit 0x000000010aa94d0b -[UILayoutContainerView layoutSubviews] + 231
13 UIKit 0x000000010a71e7a8 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1515
14 QuartzCore 0x00000001101bc456 -[CALayer layoutSublayers] + 177
15 QuartzCore 0x00000001101c0667 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 395
16 QuartzCore 0x00000001101470fb _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 343
17 QuartzCore 0x000000011017479c _ZN2CA11Transaction6commitEv + 568
18 UIKit 0x000000010a669269 __34-[UIApplication _firstCommitBlock]_block_invoke_2 + 141
19 CoreFoundation 0x000000010a174b0c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
20 CoreFoundation 0x000000010a1592db __CFRunLoopDoBlocks + 331
21 CoreFoundation 0x000000010a158a84 __CFRunLoopRun + 1284
22 CoreFoundation 0x000000010a15830b CFRunLoopRunSpecific + 635
23 GraphicsServices 0x000000010f345a73 GSEventRunModal + 62
24 UIKit 0x000000010a64f057 UIApplicationMain + 159
25 DemoRuntime 0x0000000108f645ff main + 111
26 libdyld.dylib 0x000000010dc2f955 start + 1
27 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
方案一:动态解析进行方法转发
#import "ViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = @"runtime";
// 执行work函数
[self performSelector:@selector(work:)];
}
#pragma mark - 消息转发(方法添加)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(work:))
{
// 如果是执行work函数,就动态解析,指定新的IMP
class_addMethod([self class], sel, (IMP)workMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void workMethod(id obj, SEL _cmd)
{
// 新的work函数
NSLog(@"Doing work");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
方法二 使用备用接收者
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (void)work
{
NSLog(@"[%@]%@ is working now~", self.class, self.name);
}
@end
#import "ViewController.h"
#import "Person.h"
#import <objc/runtime.h>
#import <objc/message.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = @"runtime";
// 执行work函数
[self performSelector:@selector(work)];
}
#pragma mark - 备用接收者
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 返回YES,进入下一步转发
return YES;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(work))
{
// 返回Person对象,让Person对象接收这个消息
return [Person new];
}
return [super forwardingTargetForSelector:aSelector];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
方案三 消息完整转发
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (void)work
{
NSLog(@"[%@]%@ is working now~", self.class, self.name);
}
@end
#import "ViewController.h"
#import "Person.h"
#import <objc/runtime.h>
#import <objc/message.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = @"runtime";
// 执行work函数
[self performSelector:@selector(work)];
}
#pragma mark - 消息完整转发
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 返回YES,进入下一步转发
return YES;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
// 返回nil,进入下一步转发
return nil;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"work"])
{
// 签名,进入forwardInvocation
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = anInvocation.selector;
Person *p = [Person new];
if ([p respondsToSelector:sel])
{
[anInvocation invokeWithTarget:p];
}
else
{
[self doesNotRecognizeSelector:sel];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end