/**
* Sends a message with a simple return value to an instance of a class.
*
* @param self A pointer to the instance of the class that is to receive the message.
* @param op The selector of the method that handles the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method.
*
* @note When it encounters a method call, the compiler generates a call to one of the
* functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
* Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;
* other messages are sent using \c objc_msgSend. Methods that have data structures as return values
* are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
*/
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
SEL如下
typedef struct objc_selector *SEL;
第一个参数是receiver,是一个结构体,拥有isa指针的对象
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
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;
/* Use `Class` instead of `struct objc_class *` */
第二个参数是具体的方法名字,映射到方法的C字符串。需要注意的是@selector()选择子只与函数名有关。不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。
receiver收到selector之后现在自己的方法中查找,如果没有就去父类查找,一直往上查找。
如果全都没有的话就会去做消息转发。
查不到的话就崩溃了。
那么objc_msgSend是怎么实现的呢?
在Obj-C Optimization: The faster objc_msgSend中提到了objc_msgSend是如何实现的。
#include
id c_objc_msgSend( struct objc_class /* ahem */ *self, SEL _cmd, ...)
{
struct objc_class *cls;
struct objc_cache *cache;
unsigned int hash;
struct objc_method *method;
unsigned int index;
// 当self存在的时候将self结构体的值取出来
if( self)
{
cls = self->isa;
cache = cls->cache;
hash = cache->mask;
index = (unsigned int) _cmd & hash;
do
{
method = cache->buckets[ index];
if( ! method)
// 汇编goto方法,直接跳到某一行继续执行
goto recache;
index = (index + 1) & cache->mask;
}
while( method->method_name != _cmd);
return( (*method->method_imp)( (id) self, _cmd));
}
return( (id) self);
recache:
/* ... */
return( 0);
}
方法里面最重要的是执行了一个dowhile 循环,循环查找方法是否存在。
index = (unsigned int) _cmd & hash;
SEL和IMP用映射表关联。SEL 通过hash加密之后得到他对应的IMP的地址。
接下来看Source Browser/ [objc4-680.tar.gz]
arm64:iPhone6s | iphone6s plus|iPhone6| iPhone6 plus|iPhone5S | iPad Air| iPad mini2(iPad mini with Retina Display)
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4
i386是针对intel通用微处理器32位处理器
x86_64是针对x86架构的64位处理器
模拟器32位处理器测试需要i386架构,
模拟器64位处理器测试需要x86_64架构,
真机32位处理器需要armv7,或者armv7s架构,
真机64位处理器需要arm64架构。
查看objc-msg-x86_64.s 的汇编源码
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd,...);
*
********************************************************************/
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
ENTRY _objc_msgSend
MESSENGER_START
NilTest NORMAL
GetIsaFast NORMAL // r11 = self->isa
CacheLookup NORMAL // calls IMP on success
NilTestSupport NORMAL
GetIsaSupport NORMAL
// cache miss: go search the method lists
LCacheMiss:
// isa still in r11
MethodTableLookup %a1, %a2 // r11 = IMP
cmp %r11, %r11 // set eq (nonstret) for forwarding
jmp *%r11 // goto *imp
END_ENTRY _objc_msgSend
ENTRY _objc_msgSend_fixup
int3
END_ENTRY _objc_msgSend_fixup
STATIC_ENTRY _objc_msgSend_fixedup
// Load _cmd from the message_ref
movq 8(%a2), %a2
jmp _objc_msgSend
END_ENTRY _objc_msgSend_fixedup
查看汇编的注释后将一个方法分成两个步骤来解析
第一步 查找self.isa的方法缓存,如果找到了就调用。没找到进入第二步
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
ENTRY _objc_msgSend
MESSENGER_START
// 判空处理 ,如果为空的话返回
NilTest NORMAL
// 快速获取Isa,并且放到r11寄存器上面
GetIsaFast NORMAL // r11 = self->isa
// 查找方法缓存,如果找到了就调用IMP
CacheLookup NORMAL // calls IMP on success
NilTestSupport NORMAL
GetIsaSupport NORMAL
比较重要的几个点
align 用来字节对齐
例如32 位的寄存器容量是 4 字节, 如果内存中的数据都按 4*n 字节对齐, 肯定会加快吞吐速度;
但事实并非如此, 不同大小的数据可能会让寄存器别别扭扭地去处理, 从而降低了运行速度!
如果使用对齐, 就会浪费掉一些内存空间; 其实这是一个需要权衡 "速度" 与 "内存" 得失的问题.
NilTest
/////////////////////////////////////////////////////////////////////
//
// NilTest return-type
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
// %a1 or %a2 (STRET) = receiver
//
// On exit: Loads non-nil receiver in %a1 or %a2 (STRET), or returns zero.
//
// NilTestSupport return-type
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
// %a1 or %a2 (STRET) = receiver
//
// On exit: Loads non-nil receiver in %a1 or %a2 (STRET), or returns zero.
//
/////////////////////////////////////////////////////////////////////
.macro NilTest
.if $0 == SUPER || $0 == SUPER_STRET
error super dispatch does not test for nil
.endif
.if $0 != STRET
testq %a1, %a1
.else
testq %a2, %a2
.endif
PN
jz LNilTestSlow_f
.endmacro
这个方法用来做nil判断
objc_msgSend 对应 NORMAL
objc_msgSend_fpret 对应 NilTest FPRET
objc_msgSend_fp2ret 对应 NilTest FP2RET
objc_msgSend_stret 传入的参数是NilTest STRET
test属于逻辑运算指令
功能: 执行BIT与BIT之间的逻辑运算
测试(两操作数作与运算,仅修改标志位,不回送结果).
Test对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器,结果本身不会保存。TEST AX,BX 与 AND AX,BX 命令有相同效果
/////////////////////////////////////////////////////////////////////
//
// CacheLookup return-type, caller
//
// Locate the implementation for a class in a selector's method cache.
//
// Takes:
// $0 = NORMAL, FPRET, FP2RET, STRET, SUPER, SUPER_STRET, SUPER2, SUPER2_STRET, GETIMP
// a2 or a3 (STRET) = selector a.k.a. cache
// r11 = class to search
//
// On exit: r10 clobbered
// (found) calls or returns IMP, eq/ne/r11 set for forwarding
// (not found) jumps to LCacheMiss, class still in r11
//
/////////////////////////////////////////////////////////////////////
.macro CacheLookup
// 判断传入的是否是STRET,SUPER_STRET,SUPER2_STRET类型 如果不是的话将_cmd给A2,不然给A3
.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET
movq %a2, %r10 // r10 = _cmd
.else
movq %a3, %r10 // r10 = _cmd
.endif
// and 是将后边两个操作数按位求&,加l表示后边两个操作数是4个字节32bit的
// shl 逻辑移位
// add 添加偏移量
andl 24(%r11), %r10d // r10 = _cmd & class->cache.mask
shlq $$4, %r10 // r10 = offset = (_cmd & mask)<<4
addq 16(%r11), %r10 // r10 = class->cache.buckets + offset
.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET
// cmp 比较方法是否一致
cmpq (%r10), %a2 // if (bucket->sel != _cmd)
.else
cmpq (%r10), %a3 // if (bucket->sel != _cmd)
.endif
jne 1f // scan more
// CacheHit 之前必须始终不采取“jne”指令
CacheHit $0 // call or return imp
1:
// 循环在缓存中查找方法
cmpq $$0, (%r10)
je LCacheMiss_f // if (bucket->sel == 0) cache miss
cmpq 16(%r11), %r10
je 3f // if (bucket == cache->buckets) wrap
subq $$16, %r10 // bucket--
2:
.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET
cmpq (%r10), %a2 // if (bucket->sel != _cmd)
.else
cmpq (%r10), %a3 // if (bucket->sel != _cmd)
.endif
jne 1b // scan more
// CacheHit 之前必须始终不采取“jne”指令
CacheHit $0 // call or return imp
3:
// wrap
movl 24(%r11), %r10d // r10 = mask a.k.a. last bucket index
shlq $$4, %r10 // r10 = offset = mask<<4
addq 16(%r11), %r10 // r10 = &cache->buckets[mask]
jmp 2f
// 克隆查找循环崩溃 而不是去挂起
1:
// loop
cmpq $$0, (%r10)
je LCacheMiss_f // if (bucket->sel == 0) cache miss
cmpq 16(%r11), %r10
je 3f // if (bucket == cache->buckets) wrap
subq $$16, %r10 // bucket--
2:
.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET
cmpq (%r10), %a2 // if (bucket->sel != _cmd)
.else
cmpq (%r10), %a3 // if (bucket->sel != _cmd)
.endif
jne 1b // scan more
// CacheHit 之前必须始终不采取“jne”指令
CacheHit $0 // call or return imp
3:
// double wrap - busted
.if $0 == STRET || $0 == SUPER_STRET || $0 == SUPER2_STRET
movq %a2, %a1
movq %a3, %a2
.elseif $0 == GETIMP
movq $$0, %a1
.endif
// a1 = receiver
// a2 = SEL
movq %r11, %a3 // a3 = isa
.if $0 == GETIMP
jmp _cache_getImp_corrupt_cache_error
.else
jmp _objc_msgSend_corrupt_cache_error
.endif
.endmacro
第二步:在MethodTableLookup中查找
// 第一步没有找到对应的方法时去父类查找
// cache miss: go search the method lists
LCacheMiss:
// isa still in r11
MethodTableLookup %a1, %a2 // r11 = IMP
cmp %r11, %r11 // set eq (nonstret) for forwarding
jmp *%r11 // goto *imp
END_ENTRY _objc_msgSend
ENTRY _objc_msgSend_fixup
int3
END_ENTRY _objc_msgSend_fixup
STATIC_ENTRY _objc_msgSend_fixedup
// Load _cmd from the message_ref
movq 8(%a2), %a2
jmp _objc_msgSend
END_ENTRY _objc_msgSend_fixedup
比较重要的几个点
MethodTableLookup 把receiver, selector, class三个参数传给$0,$1,r11,然后调用
__class_lookupMethodAndLoadCache3函数,函数返回的IMP 从r11给到rax中,查找成功就去调用这个IMP。
/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup classRegister, selectorRegister
//
// Takes: $0 = class to search (a1 or a2 or r10 ONLY)
// $1 = selector to search for (a2 or a3 ONLY)
// r11 = class to search
//
// On exit: imp in %r11
//
/////////////////////////////////////////////////////////////////////
.macro MethodTableLookup
MESSENGER_END_SLOW
SaveRegisters
// _class_lookupMethodAndLoadCache3(receiver, selector, class)
movq $0, %a1
movq $1, %a2
movq %r11, %a3
call __class_lookupMethodAndLoadCache3
// IMP is now in %rax
movq %rax, %r11
RestoreRegisters
.endmacro
调用了lookUpImpOrForward方法 ,在本类中查找方法,找不到之后去父类查找,全都查不到之后调用转发流程
/***********************************************************************
* lookUpImpOrForward.
* 标准查找
* initialize==NO 尝试避免使用 +initialize (但有时候失败)
* cache==NO 跳过乐观锁查找(但在别的地方还是用到缓存)
* 尽量使用 initialize==YES 和 cache==YES.
* inst 是 instance of cls 或者 a subclass , 或者 nil 如果没有已知.
* 如果 cls 是 un-initialized metaclass 那么查找一个 non-nil inst 是非常快的。
* 可能返回 _objc_msgForward_impcache. IMPs 转换成 _objc_msgForward 或者 _objc_msgForward_stret.
* 如果你不想使用转发, 使用 lookUpImpOrNil()替代.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP imp = nil;
Method meth;
bool triedResolver = NO;
// 断言锁
rwlock_assert_unlocked(&runtimeLock);
// 乐观查找
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
if (!cls->isRealized()) {
// 没有找到的话
// 加锁
rwlock_write(&runtimeLock);
// 申请class_rw_t空间。
realizeClass(cls);
// 解锁
rwlock_unlock_write(&runtimeLock);
}
if (initialize && !cls->isInitialized()) {
// _class_initialize类初始化
_class_initialize (_class_getNonMetaClass(cls, inst));
// 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:
// 添加一个读锁。防止动态添加方法,保证现场安全
rwlock_read(&runtimeLock);
// 忽略CG垃圾回收方法
if (ignoreSelector(sel)) {
imp = _objc_ignored_method;
cache_fill(cls, sel, imp);
goto done;
}
// 尝试class的cache中查找
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 尝试在methodlist中查找
meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth->imp, sel);
imp = meth->imp;
goto done;
}
// 尝试superclass caches 和 method lists中查找
curClass = cls;
while ((curClass = curClass->superclass)) {
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, curClass, imp, sel);
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;
}
}
// 获取超类方法列表
meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, curClass, meth->imp, sel);
imp = meth->imp;
goto done;
}
}
// No implementation found. Try method resolver once.
// 找不到IMP,尝试调用_class_resolveMethod方法
if (resolver && !triedResolver) {
rwlock_unlock_read(&runtimeLock);
_class_resolveMethod(cls, sel, inst);
// 不缓存结果,没有加锁所以他可以动态添加方法,添加完了之后重新走一次查找流程
triedResolver = YES;
goto retry;
}
// 找不到方法进入转发阶段
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp);
done:
rwlock_unlock_read(&runtimeLock);
// paranoia: look for ignored selectors with non-ignored implementations
assert(!(ignoreSelector(sel) && imp != (IMP)&_objc_ignored_method));
// paranoia: never let uncached leak out
assert(imp != _objc_msgSend_uncached_impcache);
return imp;
}
接下来进入事件转发流程
/********************************************************************
*
* id _objc_msgForward(id self, SEL _cmd,...);
*
* _objc_msgForward and _objc_msgForward_stret are the externally-callable
* functions returned by things like method_getImplementation().
* _objc_msgForward_impcache is the function pointer actually stored in
* method caches.
*
********************************************************************/
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.
MESSENGER_START
nop
MESSENGER_END_SLOW
jne __objc_msgForward_stret
jmp __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-stret version
movq __objc_forward_handler(%rip), %r11
jmp *%r11
END_ENTRY __objc_msgForward
调用__objc_msgForward之后调用了__objc_forward_handler方法
这个方法就是平时崩溃答应出来的文字的来源
// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
参考
Obj-C Optimization: The faster objc_msgSend
Source Browser源码