iOS – Runtime-音讯机制-objc_msgSend()

前言

本章主要介绍音讯机制-objc_msgSend的履行流程,分为音讯发送动态办法解析音讯转发三个阶段,每个阶段能够做什么。还介绍了super的本质是什么,如何调用的

1. objc_msgSend履行流程

OC中的办法调用,其实都是转换为objc_msgSend函数的调用

objc_msgSend的履行流程能够分为3大阶段

  • 音讯发送

  • 动态办法解析

  • 音讯转发

1.1 音讯发送

iOS - Runtime-音讯机制-objc_msgSend()

  • 如果是从class_rw_t中查找办法

    1. 现已排序的,二分查找
    2. 没有排序的,遍历查找
  • receiver通过isa指针找到receiverClass

  • receiverClass通过superclass指针找到superClass

1.2 动态办法解析

iOS - Runtime-音讯机制-objc_msgSend()

1.2.1 开发者能够完成以下办法,来动态添加办法完成

  • +resolveInstanceMethod: —–用于 实例办法
  • +resolveClassMethod: —–用于类办法

1.2.2 动态解析过后,会从头走“音讯发送”的流程

  • “从receiverClass的cache中查找办法”这一步开端履行

1.2.3 示例

ZSXPerson类有test办法,可是办法完成注释掉了

@interface ZSXPerson : NSObject
- (void)test;
@end
@implementation ZSXPerson
//- (void)test {
//    NSLog(@"ZSXPerson - %s", __func__);
//}
@end

此刻我们调用 test 办法发就会报错unrecognized selector sent to instance

iOS - Runtime-音讯机制-objc_msgSend()

动态办法解析阶段给类目标添加办法完成

- (void)other {
    NSLog(@"ZSXPerson - %s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        // 获取 qitafangfa
        Method method = class_getInstanceMethod(self, @selector(other));
        // 动态添加 test 办法的完成
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        // 回来YES代表有动态添加办法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

将办法完成other设置为test的办法完成,这时候能够看到不会报错了,而是履行了- (void)other办法

iOS - Runtime-音讯机制-objc_msgSend()

1.2.3.1 类办法

动态办法解析类办法也是相似的,只不过用的是+resolveClassMethod:办法,并且class_addMethod应该给元类目标添加办法。运用object_getClass(self)获取元类目标

@interface ZSXPerson : NSObject
- (void)test;
+ (void)test1;
@end
@implementation ZSXPerson
//- (void)test {
//    NSLog(@"ZSXPerson - %s", __func__);
//}
//+ (void)test1 {
//    NSLog(@"ZSXPerson - %s", __func__);
//}
- (void)other {
    NSLog(@"ZSXPerson - %s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        // 获取 qitafangfa
        Method method = class_getInstanceMethod(self, @selector(other));
        // 动态添加 test 办法的完成
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        // 回来YES代表有动态添加办法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(test1)) {
        // 获取 qitafangfa
        Method method = class_getInstanceMethod(self, @selector(other));
        // 动态添加 test 办法的完成
        class_addMethod(object_getClass(self), sel, method_getImplementation(method), method_getTypeEncoding(method));
        // 回来YES代表有动态添加办法
        return YES;
    }
    return [super resolveClassMethod:sel];
}
@end

main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        ZSXPerson *person = [[ZSXPerson alloc] init];
//        [person test];
        [ZSXPerson test1];
    }
    return 0;
}

履行成果

iOS - Runtime-音讯机制-objc_msgSend()

1.3 音讯转发

iOS - Runtime-音讯机制-objc_msgSend()

如果动态办法解析阶段没有处理,回来到音讯转发阶段

  • 首要来到forwardingTargetForSelector:办法,该办法中能够从头回来一个音讯接收者,程序将会从头履行objc_msgSend()办法,此刻音讯时发送给新的接受者
  • 如果forwardingTargetForSelector:办法没有处理,会来到methodSignatureForSelector:办法,该办法能够回来一个办法签名,回来后,程序会持续调用forwardInvocation:办法。如果methodSignatureForSelector:办法也没处理,程序就抛出异常
  • forwardInvocation:办法中,开发者能够自定义任何逻辑
  • 以上办法都有目标办法、类办法2个版别(前面能够是加号+,也能够是减号-)

1.3.1 forwardingTargetForSelector:

新建一个ZSXCat类,该类完成了- (void)test办法

@interface ZSXCat : NSObject
@end
@implementation ZSXCat
- (void)test {
    NSLog(@"ZSXCat - %s", __func__);
}
@end

完成forwardingTargetForSelector:办法,将音讯接受者转发给ZSXCat目标

@interface ZSXPerson : NSObject
- (void)test;
@end
@implementation ZSXPerson
//- (void)test {
//    NSLog(@"ZSXPerson - %s", __func__);
//}
//+ (void)test1 {
//    NSLog(@"ZSXPerson - %s", __func__);
//}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [[ZSXCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

运转成果:

iOS - Runtime-音讯机制-objc_msgSend()
调用了ZSXCat- (void)test办法

1.3.2 methodSignatureForSelector:

@implementation ZSXPerson
//- (void)test {
//    NSLog(@"ZSXPerson - %s", __func__);
//}
//- (id)forwardingTargetForSelector:(SEL)aSelector {
//    if (aSelector == @selector(test)) {
//        return [[ZSXCat alloc] init];
//    }
//    return [super forwardingTargetForSelector:aSelector];
//}
// 办法签名:回来值类型、参数类型
- (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 {
    NSLog(@"---- forwardInvocation");
}
@end

回来办法签名,来到forwardInvocation:持续履行

iOS - Runtime-音讯机制-objc_msgSend()

2. 拓展

2.1 forwardInvocation:自定义逻辑

动态办法解析阶段,+resolveClassMethod:办法是能够给音讯接受者动态添加一个`办法完成

音讯转发阶段,forwardingTargetForSelector:办法是能够从头回来一个音讯接受者,相当于是让另一个人来处理这个办法。

可是,来到methodSignatureForSelector:办法后,能够运用办法签名自定义更复杂的业务

2.1.1 办法签名阶段的其他用法

把办法调用 转发给ZSXCat目标
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
    // 把办法调用 转发给ZSXCat目标
    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];
}
运用参数

办法添加age参数- (void)test:(int)age

调用时传入参数:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZSXPerson *person = [[ZSXPerson alloc] init];
        [person test:10];
    }
    return 0;
}

办法签名运用参数

// 办法签名:回来值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test:)) {
        return [NSMethodSignature signatureWithObjCTypes:"i24@0:8i16"];
    }
    return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个办法调用,包含:办法调用者、办法签名、办法参数
// anInvocation.target  办法调用者
// anInvocation.selector  办法名
// [anInvocation getArgument:NULL atIndex:0]  参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
    // 把办法调用 转发给ZSXCat目标
//    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];
    // 取出参数。参数次序:receiver、selector、other arguments
    int age;
    [anInvocation getArgument:&age atIndex:2];
    NSLog(@"age is %d", age + 10);
}

打印成果:

iOS - Runtime-音讯机制-objc_msgSend()

2.2 音讯转发 – 类办法

在处理音讯转发的时候,会发现如果forwardingTargetForSelectormethodSignatureForSelector办法,运用+开头写法时代码提示没有这俩办法,所以有的人以为音讯转发不支持类计划

其实是支持的,把办法的-号改成+即可:

@interface ZSXPerson : NSObject
- (void)test:(int)age;
+ (void)test1;
@end
@implementation ZSXCat
- (void)test {
    NSLog(@"ZSXCat - %s", __func__);
}
+ (void)test1 {
    NSLog(@"ZSXCat - %s", __func__);
}
@end

main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        ZSXPerson *person = [[ZSXPerson alloc] init];
//        [person test:10];
        [ZSXPerson test1];
    }
    return 0;
}

ZSXPerson.m

@implementation ZSXPerson
//- (void)test:(int)age {
//    NSLog(@"ZSXPerson - %s", __func__);
//}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [[ZSXCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
//+ (id)forwardingTargetForSelector:(SEL)aSelector {
//    if (aSelector == @selector(test1)) {
//        return [ZSXCat class];
//    }
//    return [super forwardingTargetForSelector:aSelector];
//}
// 办法签名:回来值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test:)) {
        return [NSMethodSignature signatureWithObjCTypes:"i24@0:8i16"];
    }
    return [super methodSignatureForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test1)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"]; //v16@0:8 可简写为 v@:
    }
    return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个办法调用,包含:办法调用者、办法签名、办法参数
// anInvocation.target  办法调用者
// anInvocation.selector  办法名
// [anInvocation getArgument:NULL atIndex:0]  参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
//    // 把办法调用 转发给ZSXCat目标
//    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];
    // 取出参数。参数次序:receiver、selector、other arguments
//    int age;
//    [anInvocation getArgument:&age atIndex:2];
//    NSLog(@"age is %d", age + 10);
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
    // 把办法调用 转发给ZSXCat目标
    [anInvocation invokeWithTarget:[ZSXCat class]];
}
@end

2.3 super

2.3.1 示例代码

ZSXStudent继承于ZSXPersonZSXPerson继承于NSObject。如下代码打印成果是什么

ZSXPerson.h

@interface ZSXPerson : NSObject
@end

ZSXStudent.h

@interface ZSXStudent : ZSXPerson
@end

ZSXStudent.m

@implementation ZSXStudent
- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"[self class] - %@", [self class]);
        NSLog(@"[self superclass] - %@", [self superclass]);
        NSLog(@"--------------------------------");
        NSLog(@"[super class] - %@", [super class]);
        NSLog(@"[super superclass] - %@", [super superclass]);
    }
    return self;
}
@end

打印成果:

iOS - Runtime-音讯机制-objc_msgSend()
示例中,比较简单混淆的是[super class][super superclass],他们的打印成果和[self class][self superclass]一样的。

2.3.2 super本质

super调用时,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数

  • struct objc_super
  • SEL

objc_super结构体:

iOS - Runtime-音讯机制-objc_msgSend()

  • receiver是音讯接收者
  • super_class是第一个要搜索的类

superself相比,它们音讯接受者都是selfsuper会多传一个super_class,表示从super_class开端查找。

2.3.3 分析

ZSXStudent中,履行[super class],相当于履行这句:

objc_msgSendSuper({self, [ZSXPerson class]}, @selector(class));

表示:

  • 音讯接收者:self
  • ZSXPerson类目标开端
  • 查找调用class办法

class办法存在于NSObject中的,此刻不论从ZSXStudent开端查找,还是从ZSXPerson开端查找,终究都到NSObject才找到办法

class办法完成如下:

iOS - Runtime-音讯机制-objc_msgSend()

[super class][self class]他们的音讯接受者都是self,也便是ZSXStudent,因而他们打印成果都是ZSXStudentsuperclass则都是ZSXPerson

@oubijiexi