苹果官方资源opensource
objc4-838可编译联调源码
本章节研究消息发送和消息转发:
1.方法调用的实质
2.objc_msgSend和objc_msgSendSuper的区别
3.消息发送的快速查找imp过程
4.消息发送的慢速查找imp过程
5.动态方法解析过程
6.消息快速转发
7.消息慢速转发
8.动态方法解析案例、消息转发案例
本文核心疑问:怎么通过sel找到imp
前言
我们知道Objctive-C方法调用实际是给一个对象发送消息。从这句话我们不难看出必要的条件有消息的接收者,和怎么发送消息。
消息的发送过程是通过sel
找到imp
。
Runtime
框架是一套运行时api
,底层是用c/c++/汇编
写的,提供给Objective-C/swift
等使用。Runtime
分成两部分:1.类的各方面的动态配置、2.消息发送。
一、方法调用的实质
工程准备:新建一个工程
工程开启消息发送配置:target -> Build Settings -> Apple Clang - Preprocessing -> Enable Strict Checking of objc_msgSend Call
设置为NO
声明一个WJPerson
类
#import
NS_ASSUME_NONNULL_BEGIN
@interface WJPerson : NSObject
- (void)run;
- (void)walk;
+ (void)say;
@end
NS_ASSUME_NONNULL_END
#import "WJPerson.h"
@implementation WJPerson
- (void)run{
NSLog(@"%s",__func__);
}
- (void)walk{
NSLog(@"%s",__func__);
}
+ (void)say {
NSLog(@"%s",__func__);
}
@end
在main.m
#import
#import "WJPerson.h"
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
WJPerson *p = [[WJPerson alloc] init];
[p run];
}
return 0;
}
得到编译后的main.cpp
打开终端
;cd
到main.m
路径
// 编译
$ clang -rewrite-objc main.m -o main.cpp
方法调用默认会传递两个隐藏的参数:1.消息接收者id、2.消息方法名SEL
方法调用的实质是objc_msgSend函数给对象发送消息
。
二、objc_msgSend和objc_msgSendSuper的区别
objc_msgSendSuper
是发消息给对象的父类的时候就会编译出来。
来看看这么一个案例:
声明一个MyPerson类
#import
@interface MyPerson : NSObject
-(void)study;
@end
#import "MyPerson.h"
@implementation MyPerson
-(void)study {
NSLog(@"%s",__func__);
}
@end
声明一个MyTeacher类继承自MyPerson类
#import "MyPerson.h"
@interface MyTeacher : MyPerson
@end
#import "MyTeacher.h"
#import
@implementation MyTeacher
-(instancetype)init {
if (self = [super init]) {
NSLog(@"%@",[self class]);
NSLog(@"%@",[super class]);
}
return self;
}
-(void)study {
[super study];
}
@end
在ViewContriller里使用这个MyTeacher
#import "ViewController.h"
#import "MyTeacher.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
MyTeacher *t = [[MyTeacher alloc] init];
[t study];
}
@end
问题:MyTeacher里的init方法里的[self class]
和[super class]
调用分别返回的是什么?
来看看self关键字
和super关键字
有区别:
打开终端
;cd
到MyTeacher.m
路径
$ clang -rewrite-objc MyTeacher.m
找到MyTeacher.cpp的init方法:
[super class]
在编译期间会把代码转化成objc_msgSendSuper
的方式发送消息,注意消息的接收者依然是self
(MyTeacher类对象),而找class方法的imp会超类里找(这里是MyPerson的元类)
方法调用苹果官方文档给出的解释:
当遇到方法调用时,编译器生成对objc_msgSend
、objc_msgsend_street
、objc_msgSendSuper
或objc_msgsendsuper_street
函数之一的调用。
发送到对象的超类(使用super关键字)的消息使用objc_msgSendSuper
发送;其他消息使用objc_msgSend
发送。将数据结构作为返回值的方法使用objc_msgsendsuper_street
和objc_msgsend_street
发送。
自己实现一个MyTeacher里的study方法的[super study]调用;
// MyTeacher.m
- (void)study {
// [super study];
struct objc_super lg_objc_super;
lg_objc_super.receiver = self;
lg_objc_super.super_class = MyPerson.class;
void* (*objc_msgSendSuperTyped)(struct objc_super *self,SEL _cmd) = (void *)objc_msgSendSuper;
objc_msgSendSuperTyped(&lg_objc_super,@selector(study));
}
三、消息发送两种查找imp方式
- 快速 -> 缓存里找 汇编
cache_t
方法实现imp
哈希表 - 慢速 ->
c/c++
找缓存
打开objc4-838源码
四、消息发送的快速查找imp过程(汇编环节)
objc_msgSend
底层在源码上搜,是进入到汇编的源码 ENTRY _objc_msgSend
(只看arm64架构下)
- 1.当进入消息发送入口时,先判断消息接收者是否存在,不存在则重新执行objc_msgSend
再到LNilOrTagged
都做了些什么:
我们先看LReturnZero
- 2.检测指针如果为空,就立马返回。结论:
给nil发送消息不会做处理
。
接着回来看看LGetIsaDone
做了什么:
进入CacheLookup
,是一个宏定义
//在cache中通过sel查找imp的核心流程
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
// As soon as we're past the LLookupStart\Function 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\Function,
// then our PC will be reset to LLookupRecover\Function 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
//
//从x16中取出class移到x15中
mov x15, x16 // stash the original isa
//开始查找
LLookupStart\Function:
// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
//ldr表示将一个值存入到p10寄存器中
//x16表示p16寄存器存储的值,当前是Class
//#数值表示一个值,这里的CACHE经过全局搜索发现是2倍的指针地址,也就是16个字节
//#define CACHE (2 * __SIZEOF_POINTER__)
//经计算,p10就是cache
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
//真机64位看这个
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//CACHE 16字节,也就是通过isa内存平移获取cache,然后cache的首地址就是 (bucket_t *)
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
//获取buckets
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
//and表示与运算,将与上mask后的buckets值保存到p10寄存器
and p10, p11, #0x0000fffffffffffe // p10 = buckets
//p11与#0比较,如果p11不存在,就走Function,如果存在走LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
#endif
//按位右移7个单位,存到p12里面,p0是对象,p1是_cmd
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
//LSR表示逻辑向右偏移
//p11, LSR #48表示cache偏移48位,拿到前16位,也就是得到mask
//这个是哈希算法,p12存储的就是搜索下标(哈希地址)
//整句表示_cmd & mask并保存到p12
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
ldr p11, [x16, #CACHE] // p11 = mask|buckets
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
//去除掩码后bucket的内存平移
//PTRSHIFT经全局搜索发现是3
//LSL #(1+PTRSHIFT)表示逻辑左移4位,也就是*16
//通过bucket的首地址进行左平移下标的16倍数并与p12相与得到bucket,并存入到p13中
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
//ldp表示出栈,取出bucket中的imp和sel分别存放到p17和p9
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
//cmp表示比较,对比p9和p1,如果相同就找到了对应的方法,返回对应imp,走CacheHit
cmp p9, p1 // if (sel != _cmd) {
//b.ne表示如果不相同则跳转到2f
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
//向前查找下一个bucket,一直循环直到找到对应的方法,循环完都没有找到就调用_objc_msgSend_uncached
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
//通过p13和p10来判断是否是第一个bucket
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
and p10, p11, #0x007ffffffffffffe // p10 = buckets
autdb x10, x16 // auth as early as possible
#endif
// x12 = (_cmd - first_shared_cache_sel)
adrp x9, _MagicSelRef@PAGE
ldr p9, [x9, _MagicSelRef@PAGEOFF]
sub p12, p1, p9
// w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
// bits 63..60 of x11 are the number of bits in hash_mask
// bits 59..55 of x11 is hash_shift
lsr x17, x11, #55 // w17 = (hash_shift, ...)
lsr w9, w12, w17 // >>= shift
lsr x17, x11, #60 // w17 = mask_bits
mov x11, #0x7fff
lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
and x9, x9, x11 // &= mask
#else
// bits 63..53 of x11 is hash_mask
// bits 52..48 of x11 is hash_shift
lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
lsr w9, w12, w17 // >>= shift
and x9, x9, x11, LSR #53 // &= mask
#endif
// sel_offs is 26 bits because it needs to address a 64 MB buffer (~ 20 MB as of writing)
// keep the remaining 38 bits for the IMP offset, which may need to reach
// across the shared cache. This offset needs to be shifted << 2. We did this
// to give it even more reach, given the alignment of source (the class data)
// and destination (the IMP)
ldr x17, [x10, x9, LSL #3] // x17 == (sel_offs << 38) | imp_offs
cmp x12, x17, LSR #38
.if \Mode == GETIMP
b.ne \MissLabelConstant // cache miss
sbfiz x17, x17, #2, #38 // imp_offs = combined_imp_and_sel[0..37] << 2
sub x0, x16, x17 // imp = isa - imp_offs
SignAsImp x0
ret
.else
b.ne 5f // cache miss
sbfiz x17, x17, #2, #38 // imp_offs = combined_imp_and_sel[0..37] << 2
sub x17, x16, x17 // imp = isa - imp_offs
.if \Mode == NORMAL
br x17
.elseif \Mode == LOOKUP
orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
SignAsImp x17
ret
.else
.abort unhandled mode \Mode
.endif
5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
add x16, x16, x9 // compute the fallback isa
b LLookupStart\Function // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES
.endmacro
- 3.通过
类对象/元类 (objc_class)
通过内存平移得到cache
,获取buckets
,通过内存平移的方式获取对应的方法(对比sel)。
如果找到sel
就会进入CacheHit
,去return or call imp
:
如果没有找到sel
就会进入__objc_msgSend_uncached
:
- 4.如果对比
sel
找到了imp
,就会return or call imp
,如果没有sel
,则去调用_lookUpImpOrForward
接下来_lookUpImpOrForward
在汇编代码里就找不到了。
至此快速查找imp
汇编部分就结束了,接下来到了漫长查找过程:c/c++
环节。
总结消息发送快速查找
imp
(汇编):
objc_msgSend(receiver, sel, ...)
1.检查消息接收者receiver
是否存在,为nil
则不做任何处理
2.通过receiver
的isa
指针找到对应的class
类对象
3.找到class
类对象进行内存平移,找到cache
4.从cache
中获取buckets
5.从buckets
中对比参数sel
,看在缓存里有没有同名方法
6.如果buckets
中有对应的sel
-->cacheHit
--> 调用imp
7.如果buckets
中没有对应的sel
-->_objc_msgSend_uncached
->_lookUpImpOrForward
(c/c++慢速查找)
五、消息发送的慢速查找imp过程(c/c++环节)
来看看lookUpImpOrForward
函数的实现:
NEVER_INLINE
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();
if (slowpath(!cls->isInitialized())) {
// The first message sent to a class is often +new or +alloc, or +self
// which goes through objc_opt_* or various optimized entry points.
//
// However, the class isn't realized/initialized yet at this point,
// and the optimized entry points fall down through objc_msgSend,
// which ends up here.
//
// We really want to avoid caching these, as it can cause IMP caches
// to be made with a single entry forever.
//
// Note that this check is racy as several threads might try to
// message a given class for the first time at the same time,
// in which case we might cache anyway.
behavior |= LOOKUP_NOCACHE;
}
// 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.
checkIsKnownClass(cls);
// 确定当前类的继承链关系
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookup 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();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
// 如果是常量优化缓存
// 再一次从cache查找imp
// 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
// 每次判断都会把curClass的父类赋值给curClass
if (slowpath((curClass = curClass->getSuperclass()) == 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:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
下面通过截取该方法的部分来讲解:
- 1.检查类是否被初始化、是否是个已知的关系、确定继承关系等准备工作
进入了一个循环逻辑:
a.从本类的 method list
查找imp
(查找的方式是getMethodNoSuper_nolock
一会分析);
b.从本类的父类的cache
查找imp
(cache_getImp
汇编写的)
c.从本类的父类的method list
查找imp
...继承链遍历...(父类->...->根父类)
d.若上面环节有任何一个环节查找到了imp
,跳出循环,缓存方法到本类的cache
(log_and_fill_cache
);
e.直到查找到nil
,指定imp
为消息转发,跳出循环。
跳出循环后的逻辑:
如果找到了imp
,就会把imp
缓存到本类cache
里(log_and_fill_cache
):
(注意这里不管是本类还是本类的父类找到了imp,都会缓存到本类中去)
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver); // 插入缓存
}
看看在类和父类继承链中查找imp
是个什么样的查找方式的(getMethodNoSuper_nolock
):
/***********************************************************************
* getMethodNoSuper_nolock
* fixme
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static method_t *
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)
{
// 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;
}
在search_method_list_inline
里找到了method_t
就会返回出去了(search_method_list_inline
):
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
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;
}
这里就是findMethodInSortedMethodList
和findMethodInUnsortedMethodList
通过sel
找到method_t
的。这两个函数的区别就是:
-
findMethodInUnsortedMethodList
查找无序的方法列表,通过for循环遍历一个个对比sel
从而取出method_t
:
-
findMethodInSortedMethodList
查找有序的方法列表,通过二分查找对比sel
取出method_t
:
总结消息发送慢速查找
imp
(c/c++):
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
1.从本类的method list
(二分查找/遍历查找)查找imp
2.从本类的父类的cache
查找imp
(汇编)
3.从本类的父类的method list
(二分查找/遍历查找)查找imp
...继承链遍历...(父类->...->根父类)里找cache
和method list
的imp
4.若上面环节有任何一个环节查找到了imp
,跳出循环,缓存方法到本类的cache
,并返回imp
5.直到查找到nil
,指定imp
为消息转发,跳出循环,执行动态方法解析resolveMethod_locked
六、动态方法解析resolveMethod_locked
在lookUpImpOrForward
函数里有动态方法解析的入口逻辑:
当本类和本类继承链下的cache
和method list
都查找不到imp
,imp
被赋值成了_objc_msgForward_impcache
但是它没有调用,会进入动态方法解析流程,并且只会执行一次。
resolveMethod_locked
的源码声明:
/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
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); // inst:类对象 cls: 元类
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
如果类的实例调用的是实例方法:
resolveInstanceMethod(inst, sel, cls);
如果是类对象调用的类方法:
// inst:类对象 cls: 元类
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
1.类的实例方法动态方法解析
案例:声明一个WJPerson类继承NSObject,给它声明一个实例方法 -(void)say; 不写say方法实现
分析方法调用:[[WJPerson alloc] say];
流程解析:
1.缓存查找,没有找到
2.类查找/父类查找
3.动态方法解析 resolveMethod_locked
,再去找cls的元类的resolveInstacenMethod
的imp
4.如果还是没有实现,再去找根元类,直到找到 NSObject根元类
的 resolveInstacenMethod
的imp
5.系统主动给类对象
发送一条resolveInstacenMethod
消息(找到cls
的元类继承链中的resolveInstacenMethod
并调用它,并缓存到本类的cache
)(这是为了补救imp
没有找到的错误)
6.接着还会往类对象
里cache
通过sel
查找一次resolveInstacenMethod
的imp
接下来看源码:
如果 cls类对象
不是元类,就会进入resolveInstanceMethod
方法里:
/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// lookUpImpOrNilTryCache 会去元类继承链里找是否有resolveInstacenMethod的imp,如果本类中没有实现它,最终找到NSObject的根元类的系统默认实现
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// 给类对象主动发送resolveInstanceMethod消息(调用类对象里resolveInstacenMethod的imp,调用后会加入到类对象的cache里)
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, 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 = lookUpImpOrNilTryCache(inst, sel, cls);
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));
}
}
}
- a.分析
lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
/**
inst:类对象
sel:resolveInstanceMethod
cls:元类
*/
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
// 元类里 cache里找imp,肯定没有找到
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
会调用 lookUpImpOrForward
注意传递的参数。这个lookUpImpOrForward
上面解析过了
流程:
- 它会从元类的继承链里找
resolveInstanceMethod
的imp
; - 如果本类里实现了
+(Bool)resolveInstacenMethod
方法,则在元类里能找到imp
; - 如果本类里没有实现它,则最终在根元类NSObject里找
imp
,因为NSObject
类里默认声明了+(Bool)resolveInstacenMethod
方法实现。
- b.分析给类对象主动发送了
resolveInstanceMethod
的消息
让 sel+imp
绑定
流程:
- 先到本类的
cache
找resolveInstanceMethod
的imp
; - 再到本类的元类继承链中找
resolveInstanceMethod
的imp
; - 如果本类中没有实现
+(void)resolveInstanceMethod
,则会找到NSObject默认的实现; - 调用这个
imp
,并缓存到本类的cache
中去。
- c.分析
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
在a环节分析过,只是此时传递参数不一样了:
/**
inst:类的实例
sel:resolveInstanceMethod
cls:类对象
*/
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
// 类对象里找到imp,肯定是找到了,上面环节系统主动发送了一条消息
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
可以看成是从cls
类对象里的cache
找imp
,因为上面系统主动发送了一条消息了,所以类对象的cache
肯定是缓存了有imp
的,最后返回了imp出去。
2.类的类方法动态方法解析
案例:声明一个WJPerson类继承NSObject,给它声明一个实例方法 +(void)walk; 不写walk方法实现
分析方法调用:[WJPerson say];
流程解析:
1.缓存查找,没有找到
2.元类/根元类的方法列表找-(void)say
,没有找到
3.动态方法解析resolveMethod_locked
,再去找cls
的元类的resolveClassMethod
的imp
4.如果还是没有实现,就去找元类->根元类
直到找到NSObjec根元类
的resolveInstacenMethod
的imp
5.给元类发送一条消息(这里苹果做了处理,下面做源码分析),让sel+imp
绑定
接下来看源码:
/***********************************************************************
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
/**
* inst: 类对象
* cls: 元类
*/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
// lookUpImpOrNilTryCache 会去元类继承链里找是否有resolveClassMethod的imp,如果本类中没有实现它,最终找到NSObject的根元类的系统默认实现
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
// 处理元类,nonmeta赋值成类对象
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
// 给类对象发送resolveClassMethod消息,让sel+imp绑定
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(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 = lookUpImpOrNilTryCache(inst, sel, cls);
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));
}
}
}
- a.分析
lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
/***********************************************************************
* lookUpImpOrForward / lookUpImpOrForwardTryCache / lookUpImpOrNilTryCache
* The standard IMP lookup.
*
* The TryCache variant attempts a fast-path lookup in the IMP Cache.
* Most callers should use lookUpImpOrForwardTryCache with LOOKUP_INITIALIZE
*
* Without LOOKUP_INITIALIZE: tries to avoid +initialize (but sometimes fails)
* With LOOKUP_NIL: returns nil on negative cache hits
*
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use LOOKUP_NIL.
**********************************************************************/
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
// 元类的cache里找resolveClassMethod的imp,肯定是找不到的
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);// inst: 类对象 cls: 元类
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
会调用 lookUpImpOrForward
注意传递的参数。这个lookUpImpOrForward
上面解析过了
流程:
- 会从元类的继承链里找
resolveClassMethod
的imp
; - 如果本类里实现了
+(Bool)resolveClassMethod
方法,则在元类里能找到imp
; - 如果本类里没有实现它,则最终在根元类
NSObject
里找imp
,因为NSObject
类里默认声明了+(Bool)resolveClassMethod
方法实现。
- b.赋值
nonmeta
看看getMaybeUnrealizedNonMetaClass
做了什么事:
/***********************************************************************
* getMaybeUnrealizedNonMetaClass
* Return the ordinary class for this class or metaclass.
* `inst` is an instance of `cls` or a subclass thereof, or nil.
* Non-nil inst is faster.
* The result may be unrealized.
* Used by +initialize.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst)
{
static int total, named, secondary, sharedcache, dyld3;
runtimeLock.assertLocked();
ASSERT(metacls->isRealized());
total++;
// return cls itself if it's already a non-meta class
// 如果传递进来的metacls不是元类,则返回metacls,说明这时metacls是类对象
// 然后我们类方法调用的是元类,继续往下走
if (!metacls->isMetaClass()) return metacls;
// metacls really is a metaclass
// which means inst (if any) is a class
// special case for root metaclass
// where inst == inst->ISA() == metacls is possible
// 元类的isa == metacls 说明metacls是根元类(只有根元类的isa指向自己)
if (metacls->ISA() == metacls) {
Class cls = metacls->getSuperclass(); // 取根元类的父类就是 NSObject
ASSERT(cls->isRealized());
ASSERT(!cls->isMetaClass());
ASSERT(cls->ISA() == metacls); // NSObject的isa指向根元类
if (cls->ISA() == metacls) return cls; // 返回NSObject的类对象,但我们使用[WJPerson walk]; 传递进来的metacls只是 WJPerson的元类,是有意地让代码往下走
}
// use inst if available
// 如果类对象存在
if (inst) {
Class cls = remapClass((Class)inst); // 直接强转成类对象
// cls may be a subclass - find the real class for metacls
// fixme this probably stops working once Swift starts
// reallocating classes if cls is unrealized.
while (cls) {
if (cls->ISA() == metacls) {
ASSERT(!cls->isMetaClassMaybeUnrealized());
return cls;
}
cls = cls->getSuperclass();
}
#if DEBUG
_objc_fatal("cls is not an instance of metacls");
#else
// release build: be forgiving and fall through to slow lookups
#endif
}
// See if the metaclass has a pointer to its nonmetaclass.
if (Class cls = metacls->bits.safe_ro()->getNonMetaclass())
return cls;
// try name lookup
{
Class cls = getClassExceptSomeSwift(metacls->mangledName());
if (cls && cls->ISA() == metacls) {
named++;
if (PrintInitializing) {
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful by-name metaclass lookups",
named, total, named*100.0/total);
}
return cls;
}
}
// try secondary table
{
Class cls = (Class)NXMapGet(nonMetaClasses(), metacls);
if (cls) {
secondary++;
if (PrintInitializing) {
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful secondary metaclass lookups",
secondary, total, secondary*100.0/total);
}
ASSERT(cls->ISA() == metacls);
return cls;
}
}
if (Class cls = getPreoptimizedClassesWithMetaClass(metacls)) {
if (PrintInitializing) {
if (objc::inSharedCache((uintptr_t)cls)) {
sharedcache++;
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful shared cache metaclass lookups",
sharedcache, total, sharedcache*100.0/total);
} else {
dyld3++;
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful dyld closure metaclass lookups",
dyld3, total, dyld3*100.0/total);
}
}
return cls;
}
_objc_fatal("no class for metaclass %p", (void*)metacls);
}
把nonmeta
赋值成一个类对象
- c.分析给类对象主动发送了
resolveClassMethod
的消息
让 sel+imp
绑定
流程:
- 先到本类的
cache
找resolveClassMethod
的imp
; - 再到本类的元类继承链中找
resolveClassMethod
的imp
; - 如果本类中没有实现
+(void)resolveClassMethod
,则会找到NSObject
默认的实现; - 调用这个
imp
,并缓存到本类的cache
中去。
- d.分析
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
可以看成是从cls类对象
里的cache
找imp
,因为上面系统主动发送了一条消息了,所以类对象的cache
肯定是缓存了有imp
的,最后返回了imp
出去。
- e.再去查找
resolveInstanceMethod
程序继续往下走:
这个判断是,如果我们开发者没有重写去实现这个+(Bool)resolveClassMethod:(SEL)sel
,那就会进入这个if体里边:resolveInstanceMethod
这就跟上面的动态实例方法解析走一样的流程,但是注意这里的inst是类对象 cls是元类
。
接下来的操作就是,去元类 -> 根元类
方法列表查找resolveInstanceMethod
的imp
,最后在NSObject跟元类
找到。
接着系统给元类发送一条消息resolveInstanceMethod
,并且最终通过lookUpImpOrForwardTryCache
使用sel
去查找imp
一次。
不管是调用实例方法还是类方法(都没有写方法实现的情况下),都可以通过在NSObject的分类里重写+(Bool)resolveInstanceMethod:(SEL)sel
捕获到,动态指定imp
- f.最后调用imp
如果在动态方法解析过程中动态给sel
指定了imp
(此时缓存已有了),则去调用它。
ps:
1.在我们重写resolveInstanceMethod
/resolveClassMethod
,在其方法内部打印,控制台会输出两次打印信息。这是为什么呢?
第一次调用resolveInstanceMethod
是在resolveMethod_locked
里通过objc_msgSend
调用的:
第二次通过bt
打印堆栈信息,发现是消息转发__forwarding__
的慢速转发环节的方法签名methodSignatureForSelector
-> class_getInstanceMethod
-> lookupImpOrForward
-> resolveInstanceMethod
2.resolveClassMethod
的设计:
我们在调用类方法[WJTeacher say]
的时候,say
方法没有实现,并且没有重写resolveClassMethod
方法,此时在NSObject
分类里重写resolveInstanceMethod
已经是可以的。因为上面已经分析过了。
设想一下如果苹果没有设计resolveClassMethod
,那么就应该在元类里面给程序员去重写resolveInstanceMethod
,但是元类苹果并没有提供出来,谁也没见过。于是乎resolveClassMethod
设计出来的作用其实就是简化了resolveInstanceMethod
的查找流程。
附上NSObject的动态方法解析的实现:
七、消息转发
当cache
没有找到imp
,类的继承链里的方法列表都没有找到imp
,并且resolveInstanceMethod
/ resolveClassMethod
返回NO就会进入消息转发。
我们在 lookUpImpOrForward
的时候就看到 imp
被指定成了_objc_msgForward_impcache
。
它就是消息转发的流程;又到了我们的源码汇编阶段:
看到上面截图的报错吗,是不是和我们没有找到imp方法是崩溃报错是那么熟悉。
消息转发过程只有汇编在调用,消息转发是调用CoreFoundation的代码没有开源!
看不到函数调用过程了。但是可以想办法让它开源。让它反汇编出来:
首先我们找到这个路径下的CoreFoundation
文件,把它复制到桌面
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
下载安装一个反编译软件 Hopper Disassembler v4
,将CoreFoundation
文件拖入反编译软件中得到反编译代码
接下来看两张图
第一张是我们Xcode方法调用时没有写方法实现,并且没有做动态方法解析成功的报错
第二张是反编译软件的代码
点进去__forwarding_prep_0__
再点进去看__forwarding__
到底做了什么事
得到了汇编里的__forwarding__
很长的一段代码,发现了forwardingTargetForSelector
如果类里没有实现forwardingTargetForSelector
就会 goto loc_12ca57
之后就会进入慢速转发
环节:
就会做僵尸对象判断,然后继续往下走,拿到方法签名sel: methodSignatureForSelector
,如果响应了方法名称还会继续往下走,做一些方法签名的判断等等,接着
这就是消息转发流程的汇编的调用顺序。
所以我们消息转发可以进行如下处理:
#pragma mark - 动态消息转发流程 ---- 只有汇编进行调用
// 消息快速转发
+ (id)forwardingTargetForSelector: (SEL)aSelector {
return [super forwardingTargetForSelector:aSelector];
}
// 消息慢速转发
+ (NSMethodSignature *)methodSignatureForSelector: (SEL) aSelector {
if (aSelector == @selector(walk)) {
return [NSMethodSignature signatureWithObjCTypes:@"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation: (NSInvocation *)anInvocation {
NSString *str = @"我是xxx";
anInvocation.target = [WjStudent class];
[anInvocation setArgument:&str atIndex:2];
anInvocation.selector = @selector(run:);
[anInvocation invoke];
}
八、动态方法解析/消息转发 的案例
1.实例方法的动态方法解析
WJPerson.h
#import
@interface WJPerson : NSObject
@end
WJPerson.m
#import "WJPerson.h"
@implementation WJPerson
- (void)personTalk {
NSLog(@"%s", __func__);
}
@end
WJTeacher.h
#import
@interface WJTeacher : NSObject
- (void)talk;
@end
WJTeacher.m
#import "WJTeacher.h"
#import "WJPerson.h"
#import
@implementation WJTeacher
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(sel));
if (sel == @selector(talk)) {
IMP imp = class_getMethodImplementation(self, @selector(teacherTalk));
// 绑定别的类的imp也是可以的
// IMP imp = class_getMethodImplementation([WJPerson class], @selector(personTalk));
class_addMethod(self.class, sel, imp, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)teacherTalk {
NSLog(@"%s", __func__);
}
@end
main.m
#import
#import "WJTeacher.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
WJTeacher *t = [WJTeacher alloc];
[t talk];
}
return 0;
}
2.类方法的动态方法解析
WJTeacher.h
#import
@interface WJTeacher : NSObject
+ (void)say;
@end
WJTeacher.m
#import "WJTeacher.h"
#import "WJPerson.h"
#import
@implementation WJTeacher
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(sel));
if (sel == @selector(say)) {
IMP imp = class_getMethodImplementation(self, @selector(tracherSay));
class_addMethod(objc_getMetaClass("WJTeacher"), sel, imp, "v@:"); // 元类的sel+imp绑定 = 类的类方法
return YES;
}
return [super resolveClassMethod:sel];
}
- (void)tracherSay {
NSLog(@"%s", __func__);
}
@end
main.m
#import
#import "WJTeacher.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[WJTeacher say];
}
return 0;
}
3.消息快速转发:
WJPerson.h
#import
@interface WJPerson : NSObject
@end
WJPerson.m
#import "WJPerson.h"
@implementation WJPerson
- (void)talk {
NSLog(@"%s", __func__);
}
+(void)say {
NSLog(@"%s", __func__);
}
@end
WJTeacher.h
#import
@interface WJTeacher : NSObject
- (void)talk;
+ (void)say;
@end
WJTeacher.m
#import "WJTeacher.h"
#import "WJPerson.h"
#import
@implementation WJTeacher
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s", __func__);
if (aSelector == @selector(talk)) {
return [WJPerson new];
}
return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s", __func__);
if (aSelector == @selector(say)) {
return WJPerson.class;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
main.m
#import
#import "WJTeacher.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
WJTeacher *t = [WJTeacher alloc];
[t talk];
[WJTeacher say];
}
return 0;
}
4.消息慢速转发:
WJPerson.h
#import
@interface WJPerson : NSObject
@end
WJPerson.m
#import "WJPerson.h"
@implementation WJPerson
- (void)talk {
NSLog(@"%s", __func__);
}
@end
WJTeacher.h
#import
@interface WJTeacher : NSObject
- (void)talk;
@end
WJTeacher.m
#import "WJTeacher.h"
#import "WJPerson.h"
#import
@implementation WJTeacher
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s", __func__);
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s", __func__);
WJPerson *p = [WJPerson alloc];
if ([self respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self];
} else if ([p respondsToSelector:anInvocation.selector]){
[anInvocation invokeWithTarget:p];
} else {
NSLog(@"%@没有imp", NSStringFromSelector(anInvocation.selector));
}
}
@end
main.m
#import
#import "WJTeacher.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
WJTeacher *t = [WJTeacher alloc];
[t talk];
}
return 0;
}