Runtime 学习心得

220 阅读11分钟

Runtime简介

Runtime其实是一套由C语言API组合成的库,它会尽可能的把代码的决策过程推迟到运行时。
对于C语言,函数的调用在编译的时候会决定调用哪个函数。
对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。OC代码最终会转换成底层Runtime的代码,在运行时来根据函数名称找对应的函数。
它基本是用C和汇编写的,属于1个C语言库,包含了很多底层的C语言API,如跟类、成员变量、方法相关的API。

Runtime消息传递

当一个对象调用方法[obj msg],编译器会转换成这样objc_msgSend(obj, sel_registerName("msg"))

Student *s = [Student new];
//以下三种方式都能调用 run 方法
[s run];
objc_msgSend(s, sel_registerName("run"));
objc_msgSend(s, @selector(run));

调用方法流程

  • 首先,通过 obj 的 isa 指针找到它的 class ;
  • 在 class 的方法列表中找 run;
  • 如果 class 中没到 run,则沿着继承链往上找;
  • 一旦找到 run 这个函数,就去执行它的实现IMP
  • 方法查找流程图

一些Runtime的术语

要想了解Runtime的机制,我们必须先了解Runtime的一些术语,他们都对应着数据结构

SEL(objc_selector)

它是selector在 Objc 中的表示(Swift 中是 Selector 类)。selector 是方法选择器,其实作用就和名字一样,日常生活中,我们通过人名辨别谁是谁,注意 Objc 在相同的类中不会有命名相同的两个方法。selector 对方法名进行包装,以便找到对应的方法实现。它的数据结构是

typedef struct objc_selector *SEL;

我们可以看出它是个映射到方法的 C 字符串,你可以通过 Objc 编译器命令@selector() 或者 Runtime 系统的 sel_registerName 函数来获取一个 SEL 类型的方法选择器。

id(objc_object)

是一个参数类型,它是指向某个类的实例的指针。定义如下:

struct objc_object { Class isa; };

typedef struct objc_object *id;

以上定义,看到objc_object 结构体包含一个 isa 指针,根据 isa 指针就可以找到对象所属的类。

Class(objc_class)

typedef struct objc_class *Class;

其实是指向objc_class结构体的指针。objc_class的数据结构如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

从objc_class可以看到,一个运行时类中关联了它的父类指针、类名、成员变量、方法、缓存以及附属的协议。 其中objc_ivar_listobjc_method_list分别是成员变量列表和方法列表:

// 单个成员
struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}      

// 成员变量列表
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}

// 单个方法
struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
} 

// 方法列表
struct objc_method_list {
    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
} 

objc_ivar_list结构体用来存储成员变量的列表,而objc_ivar则是存储了单个成员变量的信息;同理,objc_method_list结构体存储着方法数组的列表,而单个方法的信息则由objc_method结构体存储。

下图实现是 super_class 指针,虚线时 isa 指针。而根元类的父类是 NSObject,isa指向了自己。而 NSObject 没有父类,所以指向nil。

Method(objc_method)

typedef struct objc_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

objc_method 存储了方法名,方法类型和方法实现:

  • method_name:方法名
  • method_types:方法类型
  • method_imp:方法实现

Ivar(objc_ivar)

typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
} 

Ivar是表示成员变量的类型,其中ivar_offset是基地址偏移字节。

IMP

typedef id (*IMP)(id, SEL, ...);

它就是一个函数指针,这是由编译器生成的。当你发起一个ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。

你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 idSEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 idSEL 参数就能确定唯一的方法实现地址。

而一个确定的方法也只有唯一的一组 idSEL 参数。

Cache(objc_cache)

typedef struct objc_cache *Cache

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。


Runtime查找方法的详细流程

汇编部分

向对象发送消息的时候系统会调用汇编的_objc_msgSend方法

	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
	MESSENGER_START
    /// x0 recevier
    // 消息接收者  消息名称
	cmp	x0, #0			// nil check and tagged pointer check
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
	ldr	x13, [x0]		// x13 = isa
	and	x16, x13, #ISA_MASK	// x16 = class	
LGetIsaDone:
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

LNilOrTagged:
	b.eq	LReturnZero		// 检测是否为空// nil check

	// tagged
	mov	x10, #0xf000000000000000
	cmp	x0, x10
	b.hs	LExtTag        // 检测是否为taggedpointer(小数据)类型
	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]
	b	LGetIsaDone //isa处理完毕

LExtTag:
	// 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 //isa处理完毕
	
LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	MESSENGER_END_NIL
	ret    // 如果是nil就返回

	END_ENTRY _objc_msgSend

流程如下

  • 首先拿到对象及sel,cmp x0, #0
  • 进入LNilOrTagged后先调用LReturnZero检测对象及sel是否为nil,如果为nil直接返回
  • 然后进入LExtTag检测对象是否为小数据类型,如果是则进入到LGetIsaDone调用CacheLookup
  • 对象不是nil且不是taggedpointer类型,则直接调用CacheLookup去查找缓存是否存在IMP
    • CacheLookup处理类型
      • CacheHit(缓存中找到IMP,返回IMP
      • CheckMiss(缓存中没找到IMP
      • add(其他地方找到,用汇编把IMP添加到缓存)

下面是CacheLookup方法的具体实现

.macro CacheHit
.if $0 == NORMAL
	MESSENGER_END_FAST
	br	x17			// call imp
.elseif $0 == GETIMP
	mov	x0, x17			// return imp
	ret
.elseif $0 == LOOKUP
	ret				// return imp via x17
.else
.abort oops
.endif
.endmacro

.macro CheckMiss
	// miss if bucket->sel == 0
.if $0 == GETIMP
	cbz	x9, LGetImpMiss
.elseif $0 == NORMAL
	cbz	x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
	cbz	x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

.macro JumpMiss
.if $0 == GETIMP
	b	LGetImpMiss
.elseif $0 == NORMAL
	b	__objc_msgSend_uncached
.elseif $0 == LOOKUP
	b	__objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

.macro CacheLookup
	// x1 = SEL, x16 = isa
	ldp	x10, x11, [x16, #CACHE]	// x10 = buckets, x11 = occupied|mask
	and	w12, w1, w11		// x12 = _cmd & mask
	add	x12, x10, x12, LSL #4	// x12 = buckets + ((_cmd & mask)<<4)

	ldp	x9, x17, [x12]		// {x9, x17} = *bucket
1:	cmp	x9, x1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: x12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	x12, x10		// wrap if bucket == buckets
	b.eq	3f
	ldp	x9, x17, [x12, #-16]!	// {x9, x17} = *--bucket
	b	1b			// loop

3:	// wrap: x12 = first bucket, w11 = mask
	add	x12, x12, w11, UXTW #4	// x12 = buckets+(mask<<4)

	// Clone scanning loop to miss instead of hang when cache is corrupt.
	// The slow path may detect any corruption and halt later.

	ldp	x9, x17, [x12]		// {x9, x17} = *bucket
1:	cmp	x9, x1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: x12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	x12, x10		// wrap if bucket == buckets
	b.eq	3f
	ldp	x9, x17, [x12, #-16]!	// {x9, x17} = *--bucket
	b	1b			// loop

3:	// double wrap
	JumpMiss $0
	
.endmacro
  • 结果
    • 在缓存中找到IMP缓存,调用IMP
    • 在缓存中没找到IMP缓存,调用objc_msgSend_uncached进行查找

__objc_msgSend_uncached方法中调用了MethodTableLookup,即开始通过方法列表去寻找IMP

.endmacro

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band x16 is the class to search
	
	MethodTableLookup
	br	x17

	END_ENTRY __objc_msgSend_uncached


	STATIC_ENTRY __objc_msgLookup_uncached
	
// MethodTableLookup
.macro MethodTableLookup
	
	// push frame
	stp	fp, lr, [sp, #-16]!
	mov	fp, sp

	// save parameter registers: x0..x8, q0..q7
	sub	sp, sp, #(10*8 + 8*16)
	stp	q0, q1, [sp, #(0*16)]
	stp	q2, q3, [sp, #(2*16)]
	stp	q4, q5, [sp, #(4*16)]
	stp	q6, q7, [sp, #(6*16)]
	stp	x0, x1, [sp, #(8*16+0*8)]
	stp	x2, x3, [sp, #(8*16+2*8)]
	stp	x4, x5, [sp, #(8*16+4*8)]
	stp	x6, x7, [sp, #(8*16+6*8)]
	str	x8,     [sp, #(8*16+8*8)]

	// receiver and selector already in x0 and x1
	mov	x2, x16
	bl	__class_lookupMethodAndLoadCache3
	

__class_lookupMethodAndLoadCache3这个方法调用之后就开始C部分的查找,汇编部分到此结束

C语言部分

_class_lookupMethodAndLoadCache3方法实现,调用了lookUpImpOrForward

// _class_lookupMethodAndLoadCache3
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

// lookUpImpOrForward
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();
    
    // Optimistic cache lookup
    if (cache) { // 判断是否有缓存
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.read();
    // 判断这个类是否已经实现
    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }
    // 当前Class如果没有初始化,就进行初始化操作
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won`t happen. 2778172
    }

    
 retry:    
    runtimeLock.assertReading();

    // Try this class`s cache.
    // 尝试从该Class对象的缓存中查找,如果找到,就跳到done处返回该方法
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class`s method lists.
    // 尝试从该Class对象的方法列表中查找,如果找到,就跳到done处返回该方法
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    // 尝试从该Class的父类方法列表和缓存中查找
    {
        unsigned attempts = unreasonableClassCount();
        // 沿着继承链往上找
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            // 在当前superclass对象的缓存进行查找
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    // 如果在当前superclass的缓存里找到了方法,就调用log_and_fill_cache进行方法缓存
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don`t cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            // 在当前superclass对象的方法列表中进行查找
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                // 如果在当前superclass的方法列表中找到了方法,就调用log_and_fill_cache进行方法缓存
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }
    // No implementation found. Try method resolver once.
    // 没有找到IMP,尝试进行方法解析
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don`t cache the result; we don`t hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn`t help. 
    // Use forwarding.
    // 如果方法解析不成功,就进行消息转发
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

动态方法解析

// No implementation found. Try method resolver once.
    // 没有找到IMP,尝试进行方法解析
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don`t cache the result; we don`t hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }
    
// 核心方法_class_resolveMethod
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        // 实例方法
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        // 类方法
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            // 类方法动态解析调用完之后为什么还要调用实例方法的动态解析?
            // 因为在元类中子类的类方法是以实例方法的姿态存在
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

// 实例方法解析
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    // 查看cls的meta-class对象的方法列表里面是否有SEL_resolveInstanceMethod函数,
    // 也就是看是否实现了+(BOOL)resolveInstanceMethod:(SEL)sel方法
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        // 如果没找到,直接返回
        return;
    }
    // 如果找到,则通过objc_msgSend调用一下+(BOOL)resolveInstanceMethod:(SEL)sel方法完成里面的动态增加方法的步骤
    BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn`t fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform(“RESOLVE: method %c[%s %s] ”
                         “dynamically resolved to %p”, 
                         cls->isMetaClass() ? `+` : `-`, 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn`t add anything?
            _objc_inform(“RESOLVE: +[%s resolveInstanceMethod:%s] returned YES”
                         “, but no new implementation of %c[%s %s] was found”,
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? `+` : `-`, 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

// 类方法解析
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    // 断言
    assert(cls->isMetaClass());
    // 查看cls类的方法列表里面是否有SEL_resolveClassMethod函数,
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        // 如果没找到,直接返回
        return;
    }
    // 如果找到,则通过objc_msgSend调用一下+(BOOL)resolveClassMethod:(SEL)sel 方法完成里面的动态增加方法的步骤
    BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn`t fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    // Cache the result (good or bad) so the resolver doesn`t fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform(“RESOLVE: method %c[%s %s] ”
                         “dynamically resolved to %p”, 
                         cls->isMetaClass() ? `+` : `-`, 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn`t add anything?
            _objc_inform(“RESOLVE: +[%s resolveClassMethod:%s] returned YES”
                         “, but no new implementation of %c[%s %s] was found”,
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? `+` : `-`, 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

消息转发

动态方法解析完后还是没找到IMP,开始消息转发

// 如果方法解析不成功,就进行消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

消息转发这里直接就是返回了一个(IMP)_objc_msgForward_impcache指针

	STATIC_ENTRY __objc_msgForward_impcache

	MESSENGER_START
	nop
	MESSENGER_END_SLOW

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	x17, [x17, __objc_forward_handler@PAGEOFF]
	br	x17
	
	END_ENTRY __objc_msgForward

参考文章:
Runtime笔记(四)—— 刨根问底消息机制
iOS Runtime详解