本篇解析版本为objc4-723
方法调用本质
在 OC 调用方法时,底层其实是一个消息发送的过程,有如下 OC 代码
objc
Person *p = [Person new];
[p sayHello];
clang 一下可以看到 C++ 实现clang -rewrite-objc main.m -o main.cpp
,得到结果如下
scss
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("sayHello"));
可以看出,在调用方法时,编译器将它转成了objc_msgSend
消息发送了, 先说结论,消息发送的过程如下
- Runtime先通过对象
p
找到isa
指针,判断isa指针是否为nil
,为nil
直接return。 - 若不为空则通过
isa
指针找到当前实例的类对象,在类对象下查找缓存是否有sayHello
方法。 - 若在类对象缓存中找到
sayHello
方法,则直接调用IMP
方法。 - 若在类对象缓存中没找到
sayHello
方法,则查找当前类对象的方法列表methodlist
,若找到方法则将其添加到类对象的缓存中。 - 若在类对象方法列表中没找到
sayHello
方法,则继续到当前类的父类中以相同的方式查找(即类的缓存->类的方法列表)。 - 若在父类中找到
sayHello
方法,则将IMP
添加到类对象缓存中。 - 若在父类中没找到
sayHello
方法,则继续查询父类的父类,直到追溯到最上层NSObject
。 - 若还是没有找到,则启用动态方法解析、备用接收者、消息转发三部曲,给程序最后一个机会
- 若还是没找到, Runtime 会抛出异常
doesNotRecognizeSelector
。
综上,方法的查询流程基本就是查询
类对象中的缓存和方法列表
->
父类中的缓存和方法列表
->
父类的父类中的缓存和方法列表
->
...->
NSObject 中的缓存和方法列表
->
动态方法解析
->
备用接收者
->
消息转发
\
下面我们结合代码,对上述流程的每一步进行解析。
isa指针
首先是 isa 指针,什么是 isa 指针?
打开 Runtime 源码,全局搜索isa_t
,锁定objc-private.h
类,里面有这么一段代码,isa指针是isa_t的实例。
c
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
};
#endif
};
OC对象的本质,每个OC对象都含有一个isa指针,__arm64__
之前,isa
仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__
架构之后,apple对isa
进行了优化,变成了一个联合体union
结构,同时使用位域来存储更多的信息。 它是通过isa
的bits
进行位运算,取出响应位置的值,runtime中的isa
是被联合体位域优化过的,它不单单是指向类对象了,而是把64位中的每一位都运用了起来,其中的shiftcls
为33位,代表了类对象的地址,其他的位都有各自的用处。
nonpointer
:表示是否对isa指针开启指针优化 0:不开启,表示纯isa指针。 1:开启,不单单是类对象的地址,isa中包含了类信息和对象的引用计数等。has_assoc
:关联对象标识位,0没有,1有,没有关联对象会释放的更快。has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象。shiftcls
:存储类指针class
的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤用来存储类指针。magic
:固定值为0xd2,用于在调试时分辨对象是否完成初始化。weakly_referenced
:表示对象是否被指向或者曾经指向一个 ARC 的弱引用变量, 没有弱引⽤的对象可以更更快释放。deallocating
:标志对象是否正在释放内存。has_sidetable_rc
:当对象的引用计数大于10,以至于无法存储在isa指针中时,用散列表sidetable去计数。extra_rc
:表示该对象的引用计数,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数⼤于 10, 则需要使⽤到has_sidetable_rc。
下面看一下实例对象、类对象、元类之间关系的经典图示:
objc_msgSend流程解析
好了现在清楚了isa
指针是怎么一回事儿了,接下来探索objc_msgSend
底层实现。 打开 Runtime 源码,全局搜索objc_msgSend(
,找到objc-msg-arm64.s
这个类
objc_msgSend的方法调用分为两部分,快查询部分(汇编
)和慢查询部分(C
)。
快查询(汇编
)
1.ENTRY _objc_msgSend
js
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
上面代码的流程如下:
- 首先
ENTRY _objc_msgSend
进入消息发送流程 - 进入
LNilOrTagged
,当数据类型是NSTaggedPointer
或消息名为空时,直接END_ENTRY _objc_msgSend
,不为空则继续执行下一步
NSTaggedPointer
类型的对象采用和isa
一样的联合体位域的方式,可直接从地址中读取出想要的值,一般当数据类型的"value"足够小时,系统会自动转换为NSTaggedPointer
类型,比如NSString
转换为NSTaggedPointerString
2.CacheLookup NORMAL
接着进入缓存查询阶段
js
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LGetIsaDone
,这一步表示isa
指针已经处理完,要开始缓存查询CacheLookup NORMAL
(从注释可知要么从缓存中找到IMP
,要么执行objc_msgSend_uncached
从方法列表中获取IMP
)
进入CacheLookup
部分,
js
//代码剔除了混淆理解的部分
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
and w12, w1, w11 // x12 = _cmd & mask
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
.endmacro
CacheLookup NORMAL
去缓存中找IMP
,会有以下2种情况CacheHit
在缓存中找到了则直接calls imp
CheckMiss
没找到则执行objc_msgSend_uncached
CacheHit
CacheHit
部分代码如下
js
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP
ret // return IMP
.elseif $0 == LOOKUP
AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP
ret // return imp via x17
.endmacro
这里如果缓存命中,则call imp
直接调用方法实现了。
CheckMiss
若没命中接着看CheckMiss
部分
js
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.endmacro
这里执行了objc_msgSend_uncached
,进入__objc_msgSend_uncached
部分
js
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
这里告诉我们要执行MethodTableLookup
js
.macro MethodTableLookup
bl __class_lookupMethodAndLoadCache3
.endmacro
这里可以得知MethodTableLookup
实际调用的是objc-runtime-new.mm
的_class_lookupMethodAndLoadCache3
方法,从这里开始便从汇编到C语言
执行了。
慢查询部分(C
)
1._class_lookupMethodAndLoadCache3
c
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
_class_lookupMethodAndLoadCache3
方法调用了lookUpImpOrForward
函数
2.lookUpImpOrForward
完整函数为IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
,代码冗长,精简为伪代码如下:
objc
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
···
···
// 首次从 _class_lookupMethodAndLoadCache3 进来,cache 为 NO
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
retry:
// 1. 首先查询类对象的缓存,存在则结束流程
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 2. 尝试从类对象中获取 method
{
Method meth = getMethodNoSuper_nolock(cls, sel);
// 2.1 若成功获得 method,则存入缓存并结束流程
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 3. 若类对象处没有获得 method,则循环遍历父类,尝试从父类获取
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
···
// 3.1 父类也是先查询缓存
imp = cache_getImp(curClass, sel);
if (imp) {
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
// 3.2 尝试从父类对象中获取 method
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 4. 如果以上都没获取到 IMP,则查看该类是否实现了动态方法解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
triedResolver = YES;
goto retry;
}
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
···
return imp;
}
getMethodNoSuper_nolock
c
static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) {
//循环遍历method列表,查询方法
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
//mlists是method_list_t类型的方法列表,根据方法排序过或未排序采用不同的方式查询
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
//具体的查询方法
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
···
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;
}
}
···
return nil;
}
log_and_fill_cache
当从class
的method_list
中获取到method
,要执行log_and_fill_cache
存储到缓存
c
static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {
cache_fill (cls, sel, imp, receiver);
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
//获取类的缓存
cache_t *cache = getCache(cls);
//这里可以知道cache是以方法名作为key
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
//在当前缓存大小大于缓存池容量的 3/4 时,需要扩大缓存,在低于 3/4 时可以正常存储
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
//满了就扩大缓存容量,扩大为2倍
cache->expand();
}
// 把方法的IMP存储到cache中
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
存储缓存的方法,系统设计了一个动态数组,在元素个数高于 3/4 时需要扩容为 2 倍。
小结
lookUpImpOrForward
查找imp
可以分以下几点:
- 尝试获取类对象缓存
imp = cache_getImp(cls, sel);
- 执行
getMethodNoSuper_nolock(cls, sel)
,尝试从类对象的method_list
中查询Method
2.1. 若存在Method
,则执行log_and_fill_cache
存入缓存
2.2. 不存在,则执行第 3 步 - 循环遍历父类,尝试从父类获取
IMP
3.1. 父类也是先查询缓存
3.2. 查询父类对象的method_list
,getMethodNoSuper_nolock(curClass, sel);
,若查询到method
则同样存入缓存,log_and_fill_cache
- 如果以上都没获取到 IMP,则触发
_class_resolveMethod
,查看该类是否实现了动态方法解析
3._class_resolveMethod
在lookUpImpOrForward
方法中,若查询类对象和其父类对象都没有找到IMP
,则执行_class_resolveMethod
方法,直接看代码
c
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
// 判断当前是否是元类
if (! cls->isMetaClass()) {
// 类,尝试找实例方法
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// 是元类,先找类方法
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 为什么这里还要查找一次呢?下面会分析
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
_class_resolveMethod
的目的是调用执行类cls
里实现的+ (BOOL)resolveInstanceMethod:
方法或+ (BOOL)resolveClassMethod:
,根据返回值判断是否有动态实现 (返回YES
表示有动态实现,比如class_addMethod(self, sel, readImp, type)
)。
_class_resolveInstanceMethod
c
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
// 若类对象没有实现 + (BOOL)resolveInstanceMethod:(SEL)sel 方法
// 则直接 return,lookUpImpOrForward 那层会执行 _cache_addForwardEntry(cls, sel) 寻找备用接受者
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
// 这里已经知道类对象实现了resolveInstanceMethod方法
// objc_msgSend发送消息调用该方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// 既然 resolveInstanceMethod 已经处理了这个 sel,就再执行一次
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));
}
}
}
_class_resolveInstanceMethod
里面调用了lookUpImpOrNil
,进而会调用到lookUpImpOrForward
函数,如果查不到会再执行_class_resolveMethod
函数,形成了递归。
若实现了动态方法解析 ,并对类增加了IMP
,则之后会再执行一次lookUpImpOrNil
,这次会找到IMP
并执行。
_class_resolveClassMethod
_class_resolveClassMethod
和 _class_resolveInstanceMethod
逻辑差不多,只不过类方法是去元类里处理。
c
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
// 去元类里面找 resolveClassMethod,没有找到直接返回空
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
// 给类发送 resolveClassMethod 消息
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// _class_getNonMetaClass 对元类进行初始化准备,以及判断是否是根元类的一些判断,有兴趣的可以自己去看看
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// 再次去查找方法
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
// 省略了一些不重要的报错信息代码
...
}
小结
_class_resolveInstanceMethod
lookUpImpOrNil
开始循环查找类、父类等是否实现了resolveInstanceMethod
,为防止死循环,NSObject
里实现了该方法,并返回NO
,表示没有进行处理objc_msgSend
发送消息给类对象调用手动实现的resolveInstanceMethod
_class_resolveClassMethod
lookUpImpOrNil
检查是否实现了resolve..
方法objc_msgSend
发送消息给类对象调用手动实现的resolveClassMethod
,注意这里还是类对象,因为没办法发送给元类,元类是苹果虚拟出来的类
Q:如果是元类,为什么调用完_class_resolveClassMethod后要再调用一次_class_resolveInstanceMethod
因为类方法在元类中是以实例的形式存在的,
正常的查找步骤如下:
Person(类方法)
->
元类(实例方法)
->
根元类(实例方法)
->
NSObject(实例方法)
如果我们有个NSObject 分类
实现了+ (BOOL)resolveInstanceMethod:(SEL)sel
处理了没有imp
的情况,就解决了没调用到目标方法的问题,所以再次调用_class_resolveInstanceMethod
其实是苹果爸爸给机会。
Q:为什么_class_resolveMethod函数里要判断非元类执行_class_resolveInstanceMethod,元类执行_class_resolveClassMethod
不要想复杂了,这里就是规定,
- 想
动态决议
类调用实例方法,就实现+ (BOOL)resolveInstanceMethod:(SEL)sel
- 想
动态决议
类调用类方法,就实现+ (BOOL)resolveClassMethod:(SEL)sel
系统内部会按需调用的。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
,当调用类方法时,cls
指向元类,inst
指向的类对象。
消息转发
如果上面的流程走完没有找到IMP
,那么还有接下来三种补救方法,即动态方法解析、备用接收者、消息转发 ,
1.动态方法解析
resolveInstanceMethod
和resolveClassMethod
是系统给我们的一次机会,让我们可以针对没有实现的 sel
进行自定义操作 示例如下:
objc
//调用对象的实例方法时,先执行resolveInstanceMethod方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
SEL readBookSel = @selector(readBook);
Method readM = class_getInstanceMethod(self, readBookSel);
IMP readImp = class_getMethodImplementation(self, readBookSel);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(self, sel, readImp, type);
}
return [super resolveInstanceMethod:sel];
}
//调用类方法时,先执行resolveClassMethod方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(run)) {
SEL readBookSel = @selector(readBook);
Method readM = class_getInstanceMethod(self, readBookSel);
IMP readImp = class_getMethodImplementation(self, readBookSel);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(self, sel, readImp, type);
}
return [super resolveClassMethod:sel];
}
2.备用接收者
如果动态方法解析 依然没有结果,就会继续寻找备用接收者 ,(id)forwardingTargetForSelector:(SEL)aSelector;
这个forwarding方法在runtime底层是通过汇编调用的,苹果并没有给出源码实现,但是我们可以通过打印runtime的方法调用信息(log),来查看runtime接下来调用了哪些方法.方式如下:
objectivec
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
[LGPerson walk];
instrumentObjcMessageSends(NO);
}
return 0;
}
这样在finder中找到/private/tmp/文件夹,找到最新的msgSends-xxxx文件,这个文件就是LGPerson调用walk方法的时候所执行的方法信息,如图所示
显而易见的,LGPerson在动态方法解析后,继续调用了NSObject的动态方法解析,在都没有imp的情况下调用了forwardingTargetForSeletor(备用接收者)方法,当备用接受者也没有实现时,会调用最后一步methodSignatureForSelector(消息转发)
举例如下:
objc
//寻找备用接收者,让别的类来提供同名的方法
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(walk)) {
return [LGStudent new];
}
return [super forwardingTargetForSelector:aSelector];
}
在forwardingTargetForSelector方法中我们可以坐哪些操作?
- 找备用接收者处理当前类没有实现的方法(注意:备用接收者的方法只能是实例方法)
- crash收集
- 防止崩溃
3.消息转发
如果备用接收者没有找到IMP方法,则执行消息转发,主要涉及以下两个方法
objc
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
在动态方法解析和备用接收者都无法找到IMP时,系统会调用methodSignatureForSelector
方法,如果方法返回nil
,则系统直接调用doesNotRecognizeSelector
方法使程序崩溃并打印崩溃信息;如果返回一个NSMethodSignature
类型的函数签名,则系统会创建一个NSInvocation
对象并调用forwardInvocation
方法,我们可以在方法中指定别的方法或其他对象去执行这个的方法。 例子如下
objc
//若返回函数签名,则继续调用forwardInvocation方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(run)) {
//指定当前类的一个方法作为IMP
// anInvocation.selector = @selector(readBook);
// [anInvocation invoke];
//指定其他类来执行这个IMP
LGStudent *student = [LGStudent new];
[anInvocation invokeWithTarget:student];
}
}