iOS runtime(2)-class结构和消息转发机制
1. class结构
一. class结构
其实类对象和元类对象的结构是相同的,元类对象是一种特殊的类对象.由于类对象和元类对象结构相同,但我们为什么感觉类对象只有对象方法列表,元类对象只有类对象列表呢,原因是不需要的数据都变为nil.
下图是class结构图
二. class_rw_t(可修改的)
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容.
image三. class_ro_t(不可修改的)
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容.
image注:在runtime的过程中会将ro中的methods和分类中的methods合并到rw中的methods中,class的bits原来的指向是指向ro的,在runtime的过程中bits的指向由指向ro改变成指向rw
四. method_t
nmethod_t的结构体是对方法\函数的封装.
struct method_t{
SEL name; //函数名
const char *types; //编码(返回值类型、参数类型)
IMP imp; //指针函数的指针(函数地址)
};
IMP代表函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull,...);
SEL代表方法\函数名,一般叫做选择器,底层结构跟
char *
类似
- 可以通过
@selector()
和sel_registerName()
获得.- 可以通过
sel_getName()
和NSStringFromSelector()
转成字符串.- 不同类中相同名字的方法,所对应的方法选择器是相同的.
typedef strct objc_selector *SEL;
types包含了函数返回值、参数编码的字符串
返回值 | 参数1 | 参数2 | ..... | 参数n |
---|
Type Encoding
image2. 方法缓存
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度.
image散列表的原理:将key传递并计算出一个index(索引).
image用key(selector)的值&_mask就是所需要的imp,如果取值&后selector和key值不相等,_mask-1后再做&的操作.存储的时候已经&_mask计算好了缓存在第几个位置,如果在计算的时候存储的位置有方法缓存,会做_mask-1后再&的操作.(_mask有个初始值,如果容量不足可以扩容,扩容的时候清空缓存).
3. 消息转发机制
一. objc_msgsend
OC的方法调用,也叫做消息机制,给方法调用者发送一条消息.
OC中的方法调用,其实都是转换成objc_msgsend函数调用的.
- objc_msgsend的流程大致分为3个阶段:
1.消息发送.
2.动态方法解析.
3.消息转发.
objc_msgSend执行流程 – 源码跟读流程
image二. objc_msgSend执行流程01-消息发送
image如果调用的是父类的方法,会把方法缓存到当前类,如果调用的是自己的方法,会把方法的缓存到自己的类中.
三. objc_msgSend执行流程02-动态方法解析
image
- 开发者可以实现以下方法,来动态添加方法实现.
- +(BOOL)resolveInstanceMethod:(SEL)sel.
- +(BOOL)resolveClassMethod:(SEL)sel.
- 动态解析过后,会重新走“消息发送”的流程.
- 从receiverClass的cache中查找方法”这一步开始执行.
demo:
#import <Foundation/Foundation.h>
@interface CSPersion : NSObject
- (void)test;
@end
#import "CSPersion.h"
#import <objc/runtime.h>
void otherC(id self, SEL _cmd) {
NSLog(@" %@-%s-%s",self,sel_getName(_cmd),__func__);
}
@implementation CSPersion
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
struct method_t *method = (struct method_t*)class_getInstanceMethod(self,@selector(other));
class_addMethod([self class], sel, method->imp, method->types);
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
struct method_t *method = (struct method_t*)class_getInstanceMethod(self,@selector(other));
class_addMethod([self class], sel,method_getImplementation(methd), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
class_addMethod([self class], sel, (IMP)otherC, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)test {
NSLog(@"test ...");
}
- (void)other {
NSLog(@"other...");
}
@end
我们有三种方式进行方法动态解析,还是建议用第二种方式,第二种方式比较清晰.
四. objc_msgSend的执行流程03-消息转发
消息转发的意思是把消息发送给别人,交给能够处理消息的人.
当- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
方法返回的签名types不为nil时,就会调用- (void)forwardInvocation:(NSInvocation *)anInvocation ;
.
生成NSMethodSignature
NSMethodSignature *signature = [[NSMethodSignature signatureWithObjCTypes:"i@:i"]];
NSMethodSignature *signature = [[MJStudent alloc] init] methodSignatureForSelector:@selector(test:)];
image
demo
@interface Cat : NSObject
- (int)test:(int)age;
@end
@implementation Cat
- (int)test:(int)age {
NSLog(@"%s",__func__);
return age * age;
}
@end
/** 消息发送 */
@interface Student : NSObject
- (void)test:(int)age;
@end
@implementation Student
//+ (BOOL)resolveInstanceMethod:(SEL)sel
//{
// class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
//}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test:)) {
// 测试一
// return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
// 测试二
// return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
// 测试三
// return [[[Cat 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
// [[[Cat alloc] init] test:15];
// 测试三
[anInvocation invokeWithTarget:[[Cat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 2.消息转发
Student *stu = [[Student alloc] init];
[stu test:10];
}
return 0;
}
五. objc_msgSend-类方法消息转发
当+ (id)forwardingTargetForSelector:(SEL)aSelector
为nil时,会继续调用+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
,如果methodSignatureForSelector
为nil,则会报一个非常经典的错误doesNotRecognizeSelector
,我们可以看出从方法我们只有在methodSignatureForSelector
为nil时才会报错.
@interface CSCat : NSObject
+ (void)test;
- (void)test;
@end
@implementation CSCat
+ (void)test {
NSLog(@"%s", __func__);
}
- (void)test {
NSLog(@"%s", __func__);
}
@end
/** 类方法的转发过程 */
@interface CSPerson : NSObject
+ (void)test;
@end
@implementation CSPerson
+ (id)forwardingTargetForSelector:(SEL)aSelector {
// objc_msgSend([[MJCat alloc] init], @selector(test))
// [[[MJCat alloc] init] test]
// 该方法显示与注释后有不同的结果
// if (aSelector == @selector(test)) {
// return [[CSCat 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
int main(int argc, const char * argv[]) {
@autoreleasepool {
[CSPerson test];
}
return 0;
}