Runtime
OC对象的本质
类
- 定义一个MyPerson类然后编译
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyPerson *p = [[MyPerson alloc] init];
[p run];
}
return 0;
}
// 在arm64下编译
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o testMain.c++
- 得到的结果的主要部分:
#ifndef _REWRITER_typedef_MyPerson
#define _REWRITER_typedef_MyPerson
typedef struct objc_object MyPerson;
typedef struct {} _objc_exc_MyPerson;
#endif
struct MyPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyPerson *p = ((MyPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MyPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
}
return 0;
}
上边代码中我们可以看到OC对象的本质其实是一个objc_object结构体
- 继续分析objc_object结构体
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
objc_object结构体里只有一个isa
继续分析Class(这里我们只看objc-runtime-new 忽略objc-runtime-old)
typedef struct objc_class *Class;
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
// 省略n多...
}
MyPerson的对象p 其实就是一个包含objc_class结构体的objc_object结构体.
方法调用的本质
- 调用一个对象的方法,其实本质就是给结构体objc_object发送消息
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
// sel_registerName("run") 相当于@selector(run)
// (id)p 消息接收者
// sel_registerName("run") 是一个IMP 相当于方法的key
也就是说
[p run];
// 等同于
objc_msgSend(p, sel_registerName("run")); // 或者objc_msgSend(p, @selector(run));
- 调用一个类的方法,相当于给objc_class发送消息
[MyPerson walk];
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyPerson"), sel_registerName("walk"));
- 调该对象的父类方法(MyPerson 的 -(void)run)
#import <objc/runtime.h>
MyStudent *s = [[MyStudent alloc] init];
struct objc_super mySuper;
mySuper.receiver = s;
mySuper.super_class = class_getSuperclass([s class]);
objc_msgSendSuper(&mySuper, @selector(run));
注意:Xcode11 中如上述 调用父类的方法会报编译错误: Too many arguments to function call, expected 0, have 2
引用stackoverflow上的回答:这里
修正后的完整版:
#import <Foundation/Foundation.h>
#import "MyPerson.h"
#import "MyStudent.h"
#import <objc/runtime.h>
#import <objc/message.h>
typedef void *(*mySendSuperType)(struct objc_super *super_class, SEL selector);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyStudent *s = [[MyStudent alloc] init];
struct objc_super mySuper;
mySuper.receiver = s;
mySuper.super_class = class_getSuperclass([s class]);
// objc_msgSendSuper(&mySuper, @selector(run));
((mySendSuperType)objc_msgSendSuper)(&mySuper, @selector(run));
}
return 0;
}
- 调用该父类的类方法(MyPerson的 +(void)walk)
struct objc_super mySuper2;
mySuper2.receiver = [s class];
mySuper2.super_class = class_getSuperclass(object_getClass([s class]));
((mySendSuperType)objc_msgSendSuper)(&mySuper2, @selector(walk));
- objc_super结构体
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
_objc_msgSend发送消息
- _objc_msgSend 是汇编部分 下面贴一下核心的代码
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
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
/********************************************************************
*
* CacheLookup NORMAL|GETIMP|LOOKUP <function>
*
* Locate the implementation for a selector in a class method cache.
*
* When this is used in a function that doesn't hold the runtime lock,
* this represents the critical section that may access dead memory.
* If the kernel causes one of these functions to go down the recovery
* path, we pretend the lookup failed by jumping the JumpMiss branch.
*
* Takes:
* x1 = selector
* x16 = class to be searched
*
* Kills:
* x9,x10,x11,x12, x17
*
* On exit: (found) calls or returns IMP
* with x16 = class, x17 = IMP
* (not found) jumps to LCacheMiss
*
********************************************************************/
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LLookupRecover$1 which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12, x1, x16 // authenticate and call imp
// ...省略n多
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
// ... 省略n多
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup
// ...省略n多
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// ... 省略n多
.endmacro
lookUpImpOrForward的实现:
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
// 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.lock();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
//
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
//
// TODO: this check is quite costly during process startup.
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// 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
}
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookpu the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
找方法的getMethodNoSuper_nolock实现:
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel
2020-05-11 12:22:27.470486+0800 TheClassStudy[5518:2012866] resolve instance method
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000100000dc5 TheClassStudy`+[MyStudent resolveInstanceMethod:](self=MyStudent, _cmd="resolveInstanceMethod:", sel="run") at MyStudent.m:23:12
frame #1: 0x00007fff7216fabb libobjc.A.dylib`resolveInstanceMethod(objc_object*, objc_selector*, objc_class*) + 80
frame #2: 0x00007fff7216f954 libobjc.A.dylib`resolveMethod_locked(objc_object*, objc_selector*, objc_class*, int) + 319
frame #3: 0x00007fff7215a2ad libobjc.A.dylib`lookUpImpOrForward + 603
frame #4: 0x00007fff72159bd9 libobjc.A.dylib`_objc_msgSend_uncached + 73
frame #5: 0x0000000100000ea6 TheClassStudy`main(argc=1, argv=0x00007ffeefbff508) at main.m:33:9
frame #6: 0x00007fff734c82e5 libdyld.dylib`start + 1
frame #7: 0x00007fff734c82e5 libdyld.dylib`start + 1
2020-05-11 12:22:57.934615+0800 TheClassStudy[5518:2012866] resolve instance method
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000100000dc5 TheClassStudy`+[MyStudent resolveInstanceMethod:](self=MyStudent, _cmd="resolveInstanceMethod:", sel="run") at MyStudent.m:23:12
frame #1: 0x00007fff7216fabb libobjc.A.dylib`resolveInstanceMethod(objc_object*, objc_selector*, objc_class*) + 80
frame #2: 0x00007fff7216f954 libobjc.A.dylib`resolveMethod_locked(objc_object*, objc_selector*, objc_class*, int) + 319
frame #3: 0x00007fff7215a2ad libobjc.A.dylib`lookUpImpOrForward + 603
frame #4: 0x00007fff72160d0e libobjc.A.dylib`class_getInstanceMethod + 51
frame #5: 0x00007fff3c0557f4 CoreFoundation`__methodDescriptionForSelector + 282
frame #6: 0x00007fff3c08eca2 CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:] + 38
frame #7: 0x00007fff3c0462d0 CoreFoundation`___forwarding___ + 408
frame #8: 0x00007fff3c0460a8 CoreFoundation`__forwarding_prep_0___ + 120
frame #9: 0x0000000100000ea6 TheClassStudy`main(argc=1, argv=0x00007ffeefbff508) at main.m:33:9
frame #10: 0x00007fff734c82e5 libdyld.dylib`start + 1
frame #11: 0x00007fff734c82e5 libdyld.dylib`start + 1
Program ended with exit code: 9
结果到这里 也就是我们经常看到的输出:
// Default forward handler halts the process.
__attribute__((noreturn, cold)) 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);
}
extern void instrumentObjcMessageSends(BOOL);
// 用这个方法查方法调用log -> Macintosh HD/private/tmp/msgSends..1234. 1234是getpid的结果
instrumentObjcMessageSends(YES);
[s run];
instrumentObjcMessageSends(NO);
代码解释:
- ENTRY _objc_msgSend // 进入汇编
- CacheLookup NORMAL, _objc_msgSend // 执行CacheLookup方法 参数传 NORMAL 也就是0
- 再看 .macro CacheLookup部分 有3种结果
- CacheHit $0 // call or return imp
- CheckMiss $0 // miss if bucket->sel == 0
- add
- 刚开始会进入CheckMiss 然后走__objc_msgSend_uncached
- __objc_msgSend_uncached 中 继续走到MethodTableLookup
- MethodTableLookup里 走到_lookUpImpOrForward //
- lookUpImpOrForward 是在objc-runtime-new.mm里 也就是说这里终于回到我们的c++,而不再是汇编部分了.
- lookUpImpOrForward 主要是找方法, 循环:一直找imp 如果cache_getImp能找到imp就万事大吉, 找不到就getMethodNoSuper_nolock 在自己的cls->data()->methods();里找(class_rw_t 里的 method_array_t 格式的 methods)
- 找到了就跳到done, 并且cache这个imp
- 没找到就去 父类里找(也是先cache_getImp缓存里找,然后找自己)
- 父类找到了,ok 我们cache这个imp
- 都没找到?? resolveMethod_locked 找resolve解决吧
- 没找到方法的情况下 我们有两次机会补救,第一次是resolveInstanceMethod/resolveClassMethod 时候处理一次 比如动态的给该类加一个IMP
- 第二次是forward时候 实现3个方法
-
- (BOOL)forwardingTargetForSelector:(SEL)aSelector.
-
- (MethodSignature *)methodSignatureForSelector:(SEL)aSelector
-
- (void)forwardInvocation:(NSInvocation *)anInvocation
-
- 转发之后也没有 结果就到我们熟悉的unrecognized selector sent to instance...了
知道了这些,我们就可以做一些动态的事情,比如创建一个没有写过的ViewController然后跳转等等....