什么是Runtime?
运行时,众所周知,Object-C
是一门动态性比较强的语言,可以办到在程序运行中动态修改、添加方法等操作,而Runtime
就是提供一套C语言的API,让开发者可以在程序运行时,做很多动态性操作。
探索objc_msgSend
我们知道,当我们调用一个方法时,实际上是给对象发送消息,底层调用的就是Runtime
的API objc_msgSend
,那么objc_msgSend
底层最终的实现流程是怎么样的呢,让我们来窥探一下
objc_msgSend
分为三大阶段:
- 消息发送阶段
- 动态方法解析阶段
- 消息转发阶段
接下来,让我们一起来深入探索它的实现:
在objc
源码中,我们搜索objc-msg-arm64
,在文件内搜索objc_msgSend
,我们可以找到它的汇编实现:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
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
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
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
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret
END_ENTRY _objc_msgSend
这里入口,cmp x0
(x0:寄存器,存放receiver(消息接受者)
) 判断传递进来的对象的非空判断,如果为空则跳转LNilOrTagged
,如果不为空则进入下一步CacheLookup
,我们搜索CacheLookup
找到:
.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
看到注释,我们可以猜到它这里是通过@selector & mask
去 类对象的缓存列表中查找方法缓存,如果没有在缓存中查到到方法,则会走CheckMiss
,我们搜索CheckMiss
:
.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
NORMAL
下,它会调用__objc_msgSend_uncached
,搜索__objc_msgSend_uncached
找到:
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
这里执行的是MethodTableLookup
,搜索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
// imp in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
.endmacro
这里会调用__class_lookupMethodAndLoadCache3
,这里__class_lookupMethodAndLoadCache3
的实现是由C
语言实现的,在汇编中,方法名为__class_lookupMethodAndLoadCache3
则在C
语言中则会少一个下划线_class_lookupMethodAndLoadCache3
,我们全局搜索_class_lookupMethodAndLoadCache3
,我们在objc-runtime-new.mm
中发现它的实现:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
它的实现:
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();
}
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.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
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.
{
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.
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, 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.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
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;
}
这里就是objc_msgSend
的实现的核心代码
首先我们先看:
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
这里进行了一次判断缓存查找,由于我们在汇编中已经查找过一次缓存了,这里的cache
传递进来则为NO,接下来,它又进行了一次缓存查找:
imp = cache_getImp(cls, sel);
if (imp) goto done;
这里则是预防在程序走到这里的时候,我们动态地添加了方法的实现,所以又检查了一边方法缓存,接下来它开始从方法列表中查找:
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
进入方法getMethodNoSuper_nolock
:
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
到这里,我们就可以看到它是用类对象找到data
,data
即为class_rw_t
,从中找到method
,这里它会循环method
,执行search_method_list
:
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#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;
}
这里分情况进行了不同方式的查找方法,当发现列表是排序的则进行二分查找,其他情况下则线性循环查找方法,如果找到这个方法,它则执行log_and_fill_cache
,将方法缓存在方法列表,然后取出该method
的imp
函数地址,执行 goto done
:
done:
runtimeLock.unlockRead();
return imp;
将函数的地址返回到汇编,执行
当没有找到method
时,则会往下走:
// Try superclass caches and method lists.
{
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.
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, 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.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
这里循环获取该类的父类,也是进行了缓存查找
、方法列表
查找,如果找到了则缓存在该类的缓存列表中,然后取出imp
返回goto done 执行。
这里用一幅图来总结这个流程:
到这里就完成了objc_msgSend
的第一步:消息发送阶段
接下来,我们来看消息动态解析阶段 :
当上面没有找到方法实现的时候则会走到以下代码:
// No implementation found. Try method resolver once.
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;
}
这里,会进行一次动态方法解析,如果我们在类中实现了resolveInstanceMethod
方法,则会调用到类的resolveInstanceMethod
,我们可以在resolveInstanceMethod
中动态添加方法实现,最终,会标记triedResolver
已经动态解析了,并goto retry
,重新进行一次消息发送
。
例如:
MJStudent *student = [[MJStudent alloc] init];
[student studentTestWithoutImpl];
这里的studentTestWithoutImpl
并没有实现:
@implementation MJStudent
- (void)studentTest {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(studentTestWithoutImpl)) {
Method method = class_getInstanceMethod(self, @selector(studentTest));
class_addMethod(self,
sel,
method_getImplementation(method),
method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
这里,当消息发送阶段走完后,没有找到方法实现,则会走到类的resolveInstanceMethod
方法,我们在这里给类对象动态添加方法studentTestWithoutImpl
的实现,实现方法就是studentTest
,所以在外面调用studentTestWithoutImpl
的时候,最终会走到studentTest
用一幅图来总结这个流程:
到这里,就完成了动态方法解析阶段
接下来我们来看消息转发阶段:
当执行了动态方法解析后,triedResolver
标记了已经动态解析,而又没找到对应的实现方法,则会重新goto retry
,重新进行一次消消息发送阶段
,然后又走到这里,发现已经标记了,就会执行到以下代码:
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
由于_objc_msgForward_impcache
是看不到它的实现的,这里直接指出它的底层实现:
首先,_objc_msgForward_impcache
底层会先调用类的forwardingTargetForSelector
方法,这个方法返回一个对戏那个,即可以处理这个消息的对象:
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(studentTestWithoutImpl)) {
return [[MJPig alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
在MJPig
中实现studentTestWithoutImpl
:
@implementation MJPig
- (void)studentTestWithoutImpl{
NSLog(@"%s",__func__);
}
@end
即将studentTestWithoutImpl
这条消息转发给了MJPig
对象,最终则会调用MJPig
的studentTestWithoutImpl
方法,最终输出:
当我们没有实现
forwardingTargetForSelector
,它会走到下一步,执行
methodSignatureForSelector
,返回封装了
type
函数描述的对象
NSMethodSignature
,当返回不为
空
时则会走到下一步执行
forwardInvocation
,执行
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(studentTestWithoutImpl)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
return [anInvocation invokeWithTarget:[[MJPig alloc] init]];
}
用一幅图来描述这个流程:
到这里, objc_msgSend的流程就结束了
Runtime 应用
一、动态创建一个类
Class classDog = objc_allocateClassPair([NSObject class], "MJDog", 0);
objc_registerClassPair(classDog);
id dogInstance = [[classDog alloc] init];
NSLog(@"%@",dogInstance);
这里声明MJDog
为NSObject
的子类,并注册该类,最后创建对象
二、遍历成员变量
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[I];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
+ (instancetype)mj_objectWithJson:(NSDictionary *)json
{
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[I];
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
[name deleteCharactersInRange:NSMakeRange(0, 1)];
// 设值
id value = json[name];
if ([name isEqualToString:@"ID"]) {
value = json[@"id"];
}
[obj setValue:value forKey:name];
}
free(ivars);
return obj;
}
三、方法应用