第三篇 Runtime消息转发

发送消息时会在对应的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为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

猜你喜欢

转载自blog.csdn.net/potato512/article/details/80947303