OC底层->objc_msgSend 消息转发

471 阅读2分钟

案例介绍

image.png

//MJPerson.h
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
- (void)test;
@end
//MJPerson.m
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"
@implementation MJPerson

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // objc_msgSend([[MJCat alloc] init], aSelector)
        return [[MJCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

//MJCat.h
#import <Foundation/Foundation.h>

@interface MJCat : NSObject
- (void)test;
@end
//MJCat.m
#import "MJCat.h"

@implementation MJCat
- (void)test
{
    NSLog(@"%s", __func__);
}
@end

//方法调用
        MJPerson *person = [[MJPerson alloc] init];
        [person test];

  • 结果打印 image.png

  • person 只有方法声明没有test的实现

  • cat 有test方法的声明也有方法的实现

  • 但是调用的时候通过forwardingTargetForSelector这个方法让person成功调用了cat里面的test方法

  • 这个forwardingTargetForSelector方法也可以用下面的两个方法来代替


// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
//    anInvocation.target 方法调用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    anInvocation.target = [[MJCat alloc] init];
//    [anInvocation invoke];

    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
}
  • 当消息转发的方法是有传参和返回值得时候
//MJPerson.h
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
- (int)test:(int)age;
@end
//MJPerson.m
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test:)) {
//        return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
        return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
//        return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // 参数顺序:receiver、selector、other arguments
//    int age;
//    [anInvocation getArgument:&age atIndex:2];
//    NSLog(@"%d", age + 10);
    
    
    // anInvocation.target == [[MJCat alloc] init]
    // anInvocation.selector == test:
    // anInvocation的参数:15
    // [[[MJCat alloc] init] test:15]
    
    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
    
    int ret;
    [anInvocation getReturnValue:&ret];
    
    NSLog(@"%d", ret);
}

@end


//MJCat.h
#import <Foundation/Foundation.h>

@interface MJCat : NSObject
- (int)test:(int)age;
@end

//MJCat.m
#import "MJCat.h"

@implementation MJCat
- (int)test:(int)age
{
    return age * 2;
}
@end

//方法调用
        MJPerson *person = [[MJPerson alloc] init];
        [person test:15];

  • 当消息转发的方法类方法时
//MJPerson.h
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
+ (void)test;
@end

//MJPerson.m
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    // objc_msgSend([[MJCat alloc] init], @selector(test))
    // [[[MJCat alloc] init] test]
    if (aSelector == @selector(test)) return [[MJCat alloc] init];

    return [super forwardingTargetForSelector:aSelector];
}

//+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
//{
//    if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
//    
//    return [super methodSignatureForSelector:aSelector];
//}
//
//+ (void)forwardInvocation:(NSInvocation *)anInvocation
//{
//    NSLog(@"1123");
//}

@end


//MJCat.h
#import <Foundation/Foundation.h>

@interface MJCat : NSObject
+ (void)test;
- (void)test;
@end


//MJCat.m
#import "MJCat.h"

@implementation MJCat

+ (void)test
{
    NSLog(@"%s", __func__);
}

- (void)test
{
    NSLog(@"%s", __func__);
}

@end


//方法调用
        [MJPerson test];


image.png

  • 开发者可以在forwardInvocation:方法中自定义任何逻辑
  • 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
  • 这个也有两种写法 image.png
  • 个人认为消息转发机制也没有啥应用场景呀,了解就好了