iOS 底层 runtime之objc_msgSend - AlvinSunny/OC-TheUnderlying GitHub Wiki
了解Objective-C
- Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大不同;(动态性:在程序运行过程中可以改变和添加一些方法、动态创建实例)
- Objective-C的动态性是由Runtime的一套API接口来实现的;
Runtime API提供的对外调用接口基本上都是C语言的,其源码由C、C++、汇编语言编写;runtime中很多经常用到的API实现一般都是通过汇编代码实现,比如:objc_msgSend()
在OC中的方法调用都转换为运行时API-->
objc_msgSend()函数的调用; objc_msgSend如果找不到合适的方法进行调用,会报unrecognized selector sent to instance(该方法找不到)的错误
- SEL: @selecter 方法选择器 和 runtime的API-->
sel_registerName()函数作用等同
消息发送
- 消息发送调用成功,是不会进入动态方法解析、消息转发;
-
- 判断消息接收者(receiver)是否为空(是否为0),是直接return;
-
- 查找缓存 -- >CacheLookup,找到直接调用结束查找;
-
- 拿到class_rw_t中的methods进行遍历(如果是有序的就直接二分查找(折半查找),无序直接for循环),找到了底层执行 goto done 返回imp,并添加到缓存中并结束查找;
-
- 当前类找不到,通过superclass找到父类的缓存和方法列表中查找;步骤和以上三条相同;
-
- 如果父类方法列表中依然没有找到,就去父类的父类中找;一旦找到基类的方法列表中还是没有,就需要进入下一个阶段:动态方法解析;
动态方法解析
- 动态方法解析调用成功,是不会进入消息转发;
底层源码:
// No implementation found. Try method resolver once. (没有实现,尝试一次方法解析器。)
if ((behavior & LOOKUP_RESOLVER) && !triedResolver) {
methodListLock.unlock();//解锁
_class_resolveMethod(cls, sel, inst); //方法解析实现
triedResolver = YES;
goto retry; //走消息发送流程:‘从receiverClass的cache中查找方法’ 这一步开始执行
}
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
static void
_class_resolveMethod(id inst, SEL sel, Class cls)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
_class_resolveInstanceMethod(inst, sel, cls);
}
}
}
-
- 判断消息接收者是否曾经进行过动态解析 是:直接走消息转发-->_objc_msgForward_impcache 否:调用[cls resolveInstanceMethod:sel]或者[cls resolveClassMethod:sel]方法来动态解析方法
-
- 解析成功 底层执行 goto retry,标记为已经动态解析;
-
- 走消息发送流程:‘从receiverClass的cache中查找方法’ 这一步开始执行
#import <objc/runtime.h>
### 一个类只做了 - (void) test 方法的声明,没有实现
- (void)other
{
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 获取其他方法
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];
}
### 一个类只做了 + (void) test 方法的声明,没有实现
void c_other(id self, SEL _cmd)
{
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一个参数是object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
@dynamic 关键字
- @dynamic是告诉编译器
不用自动生成setter和getter方法的实现、不自动生成成员变量,等到运行时在添加方法实现,不影setter和getter方法的声明;
// 提醒编译器不要自动生成setter和getter的实现、不要自动生成成员变量
@dynamic age;
void setAge(id self, SEL _cmd, int age)
{
NSLog(@"age is %d", age);
}
int age(id self, SEL _cmd)
{
return 120;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(setAge:)) {
class_addMethod(self, sel, (IMP)setAge, "v@:I");
return YES;
} else if (sel == @selector(age)) {
class_addMethod(self, sel, (IMP)age, "i@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
消息转发
知识点:
-
_objc_msgForward_impcache消息转发底层入口函数,函数内部由汇编代码实现,内部核心代码:__objc_msgForward_stret、__objc_msgForward这两个函数是汇编函数; 底层最终会调用__forwarding__方法,__forwarding__方法__objc_msgForward--> _objc_msgForward--> __forwarding__ -
_objc_msgForward: 是 IMP 类型; 用于消息转发的;
当向一个对象发送一条消息,但它并没有实现的时候会尝试做消息转发。
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band condition register is NE for stret, EQ otherwise.
je __objc_msgForward_stret
// int __forwarding__
jmp __objc_msgForward
END_ENTRY __objc_msgForward_impcache
前提:在当前消息接收者体系中找不到方法的具体实现(自己没有能力来处理,将消息转发给别人来处理)
-
- 进入 int forwarding(void *frameStackPointer, int isStret) 函数
-
-
调用forwardingTargetForSelector:方法 返回值为空:表示没有提供消息接收者,这时就需要调用methodSignatureForSelector:方法 返回值不为空:
objc_msgSend(返回值,SEL)回到第一阶段, 注意:如果返回的是当前实例就不会回到第一阶段,而是继续来到methodSignatureForSelectorforwardingTargetForSelector: 用来返回一个可以接收当前消息的消息接收者
-
-
- 调用methodSignatureForSelector:方法,要求返回方法签名 (所谓的方法签名:方法的返回值类型和参数类型)
返回值为空:调用doesNotRecognizeSelector:方法,表示这是未识别的选择器,抛出 unrecognized selector sent to instance (该方法找不到)
返回值不为空:调用forwardInvocation:方法 ,
调用forwardInvocation之前会再次进行动态方法决议(调用resolveInstanceMethod或resolveClassMethod)
- 调用methodSignatureForSelector:方法,要求返回方法签名 (所谓的方法签名:方法的返回值类型和参数类型)
返回值为空:调用doesNotRecognizeSelector:方法,表示这是未识别的选择器,抛出 unrecognized selector sent to instance (该方法找不到)
返回值不为空:调用forwardInvocation:方法 ,
-
4 forwardInvocation:方法中开发者可以自定义任何逻辑处理,这样就不会因为方法找不到而崩溃
doesNotRecognizeSelector:源码实现
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal(
"-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), 方法调用者类名
sel_getName(sel), 方法名
self 方法调用者
);
}
- int forwarding(void *frameStackPointer, int isStret) 函数底层实现是不开源的,并不是所有的runtimeAPI都开放源码,有部分深层次的API还是没有开源。
代码示例
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[XYHCat alloc] init], aSelector)
如果返回空,就会触发methodSignatureForSelector:方法
return [[XYHCat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
// return [NSMethodSignature signatureWithObjCTypes:"i@:I"];
// return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
anInvocation.target 方法调用者
anInvocation.selector 方法名
[anInvocation getArgument:NULL atIndex:0]
//此方法中的所有实现就相当于是当初调用的方法的一个实现,比如:[XYHPerson test] , test方法没有具体实现,当执行到forwardInvocation:方法时,其内部的实现最终就是test的实现
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
不带参数指定target,方法调用
// anInvocation.target = [[XYHCat alloc] init];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[[XYHCat alloc] init]];
带参数指定target,方法调用
// 参数顺序:0 --> receiver、1 --> selector、2 --> other arguments
// int age;
// [anInvocation getArgument:&age atIndex:2];
// NSLog(@"%d", age + 10);
// anInvocation.target == [[XYHCat alloc] init]
// anInvocation.selector == test:
// anInvocation的参数:15
// [[[XYHCat alloc] init] test:15]
[anInvocation invokeWithTarget:[[XYHCat alloc] init]];
int ret;
//拿到返回值
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
类方法的消息转发
和对象方法一样,但在forwardingTargetForSelector:、methodSignatureForSelector:、forwardInvocation是需要执行 '+'开头的方法
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
// objc_msgSend([XYHCat class], @selector(test))
// [[[MJCat alloc] init] test]
if (aSelector == @selector(test)) return [XYHCat class];
return [super forwardingTargetForSelector:aSelector];
}
疑问:如果在类方法的forwardingTargetForSelector:中返回的不是类对象,而是一个实例对象且有对应的实例方法,能够调用成功吗 ?为什么?
可以的,因为在这里返回的对象不管是什么对象,只要其已经实现了对应的方法都会通过objc_msgSend()函数调用成功,只要有这个方法就行;
@implementation XHYCat
+ (void)test
{
NSLog(@"%s", __func__);
}
- (void)test
{
NSLog(@"%s", __func__);
}
@end
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test))
//这么写最终会执行 - (void)test
return [[XHYCat alloc] init];
return [super forwardingTargetForSelector:aSelector];
}
-
NSProxy是一个为对象定义接口的抽象父类,并且为其它对象或者一些不存在的对象扮演了替身角色。具体的可以看下NSProxy的官方文档 说白了NSProxy就是一个万能替身 - NSProxy是专门用来做消息转发的,内部有一个target属性,定位更加精准,效率非常高;
- NSProxy和NSObject是同一个级别的类,都是基类;
特殊:
- NSProxy对象不需要调用init初始化,因为它本来就没有init方法
- 查找方法时先判断当前继承自NSProxy的类自己有没有该方法,如果没有就跳过消息发送和动态解析,直接来到消息转发阶段;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel方法必须实现
- (void)forwardInvocation:(NSInvocation *)invocation必须实现
@interface NSProxy <NSObject> {
Class isa;
}
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
应用:
- 两个对象循环引用,为了不让彼此强引用就使用NSProxy来作为其中一个中间对象:target;
+ (instancetype)proxyWithTarget:(id)target
{
XYHProxy *proxy = [XYHProxy alloc];
proxy.target = target;
return proxy;
}
//消息转发到target
//返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
//内部实现方法调用
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
ENTRY _objc_msgSend 汇编源码
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
//x0寄存器:消息接收者-->receiver
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret //ret相当于c语言的return
END_ENTRY _objc_msgSend
源码:
#define ALWAYS_INLINE inline __attribute__((always_inline)) //添加后声明的函数必然是内联函数
#define NEVER_INLINE __attribute__((noinline)) //添加后声明的函数不在是内联函数
一个快速判断方式,__builtin_expect:
__builtin_expect((x),1) 表示 x 的值为真的可能性更大
__builtin_expect((x),0) 表示 x 的值为假的可能性更大
#define fastpath(x) (__builtin_expect(bool(x), 1))
查找方法的内联函数
功能介绍:传入一个方法数组和一个方法名 返回一个method_t
ALWAYS_INLINE static method_t * static method_t * search_method_list_inline(const method_list_t *mlist, SEL sel) {
1. isFixedUp: 方法列表是否被修正和排序过 //[共享缓存中的方法列表是1(唯一的)或3(唯一并排序的)]
2. isExpectedSize:方法列表是否有预期大小
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
//在iOS 3.1之后苹果启用了共享缓存优化方案,方法查找大多都是通过二分查找算法遍历的,
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize))
{//满足二分查找条件
return findMethodInSortedMethodList(sel, mlist);
} else
{//无序方法列表的线性搜索 即:循环遍历查找
// Linear search of unsorted method list
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name() == sel) {
//线性搜索有效,而二分搜索无效
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
函数声明
bool isFixedUp() const;
函数实现
//判断是否被修正过
bool method_list_t::isFixedUp() const {
// flages 和 3 运算后的值 是否等于3
return (flags() & 0x3) == fixed_up_method_list;
}
关于fixed_up_method_list
mlist->entry size的低两位用作固定标记。
共享缓存中的方法列表是1(唯一的)或3(唯一并排序的)。
(协议方法列表没有排序,因为它们有额外的并行数据)
运行时修复的方法列表得到3。
高两位协议->标志被用作固定标记。
PREOPTIMIZED(优化前)版本:
来自共享缓存的协议是1<<30。
运行时修复协议得到1<<30。
UN-PREOPTIMIZED(预优化)版本:
来自共享缓存的协议是1<<30。
共享缓存的修复不受信任。
运行时修复协议得到3<<30。
static const uint32_t fixed_up_method_list = 3;
static const uint32_t uniqued_method_list = 1;
//判断方法数组是否能满足需要 不需要扩容,就是数组空间够不够用
bool isExpectedSize() const {
if (isSmallList())
{//用小方法表示的
return entsize() == method_t::smallSize;//entsize 是 entry size 的缩写
} else
{//用大方法表示的
return entsize() == method_t::bigSize;
}
}
//小方法列表标记
static const uint32_t smallMethodListFlag = 0x80000000;
//是否是小方法,即最高位是否是1
//0x80000000 = 10000000000000000000000000000000
bool isSmallList() const {
// flags() & 0x80000000
return flags() & method_t::smallMethodListFlag;
}
//计算占内存大小
static const auto bigSize = sizeof(struct big);
static const auto smallSize = sizeof(struct small);
// 一个“小”方法的表示,这存储了名称、类型、实现的三个相对偏移量
struct small {
//name字段的选择器要么在共享缓存中,要么在其他地方。
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP> imp;
//是否是共享缓存
bool inSharedCache() const {
return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
objc::inSharedCache((uintptr_t)this));
}
};
// 表示一个“大”的方法 ,这是传统的; 表示存放选择器类型的三个指针和实现。
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
模板类
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
uint32_t entsizeAndFlags; //标记📌
uint32_t count;
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
//flags函数实现
uint32_t flags() const {
//FlagMask:外界传进来的标记掩码 在method_list_t中为0xffff0003
//0xffff0003 = 0b11111111111111110000000000000011
return entsizeAndFlags & FlagMask;
}
}
entsizeAndFlags 在addMethod(添加方法)时赋值操作片段
struct big {
SEL name; //8字节
const char *types; //8字节
IMP imp; //8字节
};
// fixme optimize 修复优化
method_list_t *newlist;
newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
//共享缓存中的方法列表是1(唯一的)或3(唯一并排序的) fixed_up_method_list = 3
//sizeof(struct method_t::big) == 24 == 0b11000
//fixed_up_method_list == 3 == 0b00011
//赋值操作: 0b11000 | 0b00011
newlist->entsizeAndFlags = (uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
newlist->count = 1;
auto &first = newlist->begin()->big();
first.name = name;
first.types = strdupIfMutable(types);
first.imp = imp;
addMethods_finish(cls, newlist);
/***********************************************************************
* search_method_list_inline
**********************************************************************/
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
//第一个method的位置
auto first = list->begin();
auto base = first;
decltype(first) probe; //相当于mid
//把key直接转换成uintptr_t 因为修复过后的method_list_t中的元素是排过序的
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// count = 数组个数 count >>= 1 = count = count >> 1)
// count >> 1 就相当于 (count / 2) 取整
// 1.假如 count = list->count = 8 //2 count = 7 >> 1 = 3
for (count = list->count; count != 0; count >>= 1) {
/*
1. 首地址 + (下标) //地址偏移 中间值 probe = base + 4
2. 中间值 probe = base(首地址)+ 6
*/
probe = base + (count >> 1);
//获取中间的sel的值也是强转后的值
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) { // 如果 目标key == 中间位置的key 匹配成功
//分类覆盖,分类中有相同名字的方法,如果有分类的方法我们就获取分类的方法,多个分类看编译的顺序
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
//返回方法的地址
return &*probe;
}
//如果 keyValue > 中间的位置的值
if (keyValue > probeValue) {
/*
1.base = probe + 1 = 4 + 1 = base(首地址) + 5 向上移 一位
2.base = probe + 1 ;向上移 一位
*/
base = probe + 1;
// 8 -1 = 7 因为比过一次没中 然后循环
count--;//查询完没找到返回nil
}
}
return nil;
}
以上代码就是折半查找法的代码,真的很强,那么,代码看的不够直观,我们尝试用几张图来阐述一下上面的过程,让大家一起来领略下 折半查找法的魅力。





