objc_msgSend

167 阅读3分钟

1.引言

Objective-C是一门动态的语言,动态的语言也就是说我们可以在程序运行的时候对我们的类和变量进行修改,改变一些数据结构,添加删除一些函数等,在编译的时候,我们并不知道变量的具体数据类型,也不知道真正要调用是那个函数,只有在运行的阶段才会去检查这个变量的数据类型,才会根据函数名找到具体要调用函数实现,这样就把决定性的工作,从编译阶段变成运行阶段,从而使Objective-C这门语言更加灵活;其中runtime就是实现Objective-C语言动态的api,runtime实际有如下两个核心:

  1. 类的各方面的动态配置,如添加方法,属性,修改成员变量值等;
  2. 消息的传递-可以分为消息的发送和消息的转发; 消息发送其实就是runtime通过sel去查找imp的过程,找到imp(函数指针)就可以去实现对应的方法,在编译的阶段就会把这个方法转换成objc_msgSend函数;

2.objc_msgSend

2.1 通过clang命令编译方法

例如有如下LGPerson类中有如下方法:

@interface LGPerson : NSObject
- (void)study:(NSString *)name;
- (void)happy;
+ (void)eat;
@end

调用上面方法如下:

LGPerson *p = [LGPerson alloc];
[p study:@"name"];
[p happy];
[LGPerson eat];

clang编译后得到如下:

((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("study:"), (NSString *)&__NSConstantStringImpl__var_folders__z_f_xb_0sn58n6kvy_gmch3kv40000gn_T_main_1b65b3_mi_3);
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("happy"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("eat"));

objc_msgSend 都会带有两个默认的参数:

  • 第一参数是消息的接收着;
  • 第二个参数是消息的方法名(sel); 手动调用用
  1. 导入头文件<objc/message.h>
  2. 手动调用
  • ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("happy"));
  • ((void ()(id, SEL))(void)objc_msgSend((id)p,NSSelectorFromString(@"happy"));

2.2 查看官方文档

官方描述:

When it encounters a method call, the compiler generates a call to one of the functions objc_msgSendobjc_msgSend_stretobjc_msgSendSuper, or objc_msgSendSuper_stret. Messages sent to an object’s superclass (using the super keyword) are sent using objc_msgSendSuper; other messages are sent using objc_msgSend. Methods that have data structures as return values are sent using objc_msgSendSuper_stret and objc_msgSend_stret.

  • objc_msgSend_stret 返回值是结构体
  • objc_msgSendSuper 发消息给父类的时候
  • objc_msgSend_fpret 返回值是浮点类型

2.3 objc_msgSendSuper

2.3.1 经典面试题

有一个类LGTeacher继承LGPerson,如下方法:

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

输出结果都是:LGTeacher

通过clang编译[super class]后如下

(__rw_objc_super *, SEL)(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("class"))
  • [self class]编译为objc_msgSend;
  • [super class] 编译为objc_msgSendSuper 通官方文档objc_msgSendSuper需要的参数和上面编译结果可以看到,objc_msgSendSuper也需要两个参数:
  • sel消息的的方法名称
  • objc_super结构体,有两个参数方法的接收者receiver是self,super_class当前方法的父类; 可以接受者是当前类,只是开始找这个方法是从父类开始的;

2.3.2 自己实现super关键字

  - (void)study {
//    [super study];
    struct objc_super lg_objc_super;
    lg_objc_super.receiver = self;
    lg_objc_super.super_class = LGPerson.class;

    void* (*objc_msgSendSuperTyped)(struct objc_super *self,SEL _cmd) = (void *)objc_msgSendSuper;
    objc_msgSendSuperTyped(&lg_objc_super,@selector(study));
}

如果修改super_class就会从当前的类中去找这个方法;

3.objc_msgSend分析

3.1 消息的快速查找流程

消息的快速查找是用汇编来写,汇编性能更高,更接近底层系统底层语言; objc_msgSend(receiver, sel, ...)快速查找步骤

  1. 判断receiver是否存在;
  2. 通过receiver的isa指针找到对应的class;
  3. 找到class后根据内存平移找到cache;
  4. 通过cache找到缓存方法的桶子buckets;
  5. 遍历buckets找是否有sel
  6. 如果在buckets找到sel调用cacheHit,然后调用imp;
  7. 如果没有缓存命中调用_objc_msgSend_uncached

3.2 消息的慢速查找流程

  1. 调用_objc_msgSend_uncached方法
  2. 进入MethodTableLookup
  3. 调用lookUpImpOrForward
  4. 查找当前类的methodList,如果没有找到查找父类的cache,在查找父类的methodList,一直到父类为nil,如果一直没有找到会调用消息的转发forword_imp 其中查找方法列表中是调用getMethodNoSuper_nolock(Class cls, SEL sel)方法->search_method_list_inline(const method_list_t *mlist, SEL sel)->findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)方法;在findMethodInSortedMethodList中是用二分查找查找方法的;

总结

  • objc_msgSendobjc_msgSendSuper的区别是objc_msgSend是从当前类开始找这个方法,而objc_msgSendSuper是从父类中开始找这个方法;