前言
方法在日常开发中被我们广泛使用,但是我们从来不知道它的底层实现是怎么样的。今天我们将通过这篇文章一起来探索方法在底层中的实现,动态方法决议
。
实例查找
首先我们通过一个案例来进行分析。
@interface TPerson : NSObject
- (void)formatPerson;
@end
@implementation TPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
TPerson * tp = [TPerson alloc];
[tp formatPerson];
}
return 0;
}
有一个对象 TPerson
,在它里面声明了一个- (void)formatPerson
方法,但是在.m
文件中并没有实现。接下来我们运行代码。
-[TPerson formatPerson]: unrecognized selector sent to instance 0x101204b80
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TPerson formatPerson]: unrecognized selector sent to instance 0x101204b80'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2049687b __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00000001002fbd40 objc_exception_throw + 48
2 CoreFoundation 0x00007fff2051938d -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff203fe90b ___forwarding___ + 1448
4 CoreFoundation 0x00007fff203fe2d8 _CF_forwarding_prep_0 + 120
5 KCObjcBuild 0x0000000100003f20 main + 64
6 libdyld.dylib 0x00007fff2033ef5d start + 1
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TPerson formatPerson]: unrecognized selector sent to instance 0x101204b80'
terminating with uncaught exception of type NSException
就出现了一个经典的unrecognized
崩溃信息。通过前几篇文章我们知道了当消息进入慢速查找流程时,会走到核心方法lookUpImpOrForward
中。
从lookUpImpOrForward
方法中我们可以得知,当找不到imp
时,会被赋值成默认的(IMP)_objc_msgForward_impcache
。接下来我们全局搜索_objc_msgForward_impcache
。
继续全局搜索__objc_msgForward
继续全局搜索__objc_forward_handler
,发现并没有找到它的实现。我们尝试删掉一个_
再试一试呢?
在objc-runtime.mm
目录下找到了实现。仔细一看这里不就是控制台打印的报错信息么。
动态方法决议
在慢速查找流程中,如果没有找到imp
时,会走到动态方法决议
流程中。
resolveMethod_locked
这个函数中有三个关键函数:1、resolveInstanceMethod
、resolveClassMethod
、lookUpImpOrForwardTryCache
。接下来就对他们进行探究。
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls) // inst: 对象 cls: 类
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 检查 cls 的元类是否实现 resolveInstanceMethod 方法。如果没有实现也不会返回。因为系统默认实现了并且返回 NO。
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// 发送消息调用 resolveInstanceMethod 方法,通过 objc_msgSend 发送消息。接收者是 cls,说明是类方法。
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
// 查找 sel 对应的 imp,如果没有就把 forward_imp 赋值给 imp,并且插入缓存
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
// resolved 和 imp 存在即说明动态添加了。
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));
}
}
}
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls) // inst: 对象 cls: 类
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
// 检查 cls 的元类是否实现 resolveClassMethod 方法。如果没有实现也不会返回。因为系统默认实现了
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
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);
}
}
// 向类中发送消息
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));
}
}
}
lookUpImpOrForwardTryCache
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
_lookUpImpTryCache
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
// cls 类是否初始化
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
// 如果没有初始化就在 lookUpImpOrForward 中查找
return lookUpImpOrForward(inst, sel, cls, behavior);
}
// 缓存中查找 sel 对应的 imp
IMP imp = cache_getImp(cls, sel);
// imp 存在 走都 done
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
// imp 不存在,进入慢速查找流程
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
代码探究 resolveInstanceMethod 和 resolveClassMethod
resolveInstanceMethod实例方法动态决议
添加如下代码
@interface TPerson : NSObject
- (void)formatPerson;
@end
@implementation TPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@" --- resolveInstanceMethod : %@ - %@",self, NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
TPerson * tp = [TPerson alloc];
[tp formatPerson];
}
return 0;
}
在 TPerson.m
中实现 + (BOOL)resolveInstanceMethod:(SEL)sel
方法,然后运行。
打印:
--- resolveInstanceMethod : TPerson - formatPerson
--- resolveInstanceMethod : TPerson - formatPerson
-[TPerson formatPerson]: unrecognized selector sent to instance 0x10072faa0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TPerson formatPerson]: unrecognized selector sent to instance 0x10072faa0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2049687b __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00000001002fbd40 objc_exception_throw + 48
2 CoreFoundation 0x00007fff2051938d -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff203fe90b ___forwarding___ + 1448
4 CoreFoundation 0x00007fff203fe2d8 _CF_forwarding_prep_0 + 120
5 KCObjcBuild 0x0000000100003e10 main + 64
6 libdyld.dylib 0x00007fff2033ef5d start + 1
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TPerson formatPerson]: unrecognized selector sent to instance 0x10072faa0'
terminating with uncaught exception of type NSException
(lldb)
发现在程序崩溃前调用了resolveInstanceMethod
方法。但是为什么这里会打印两次呢?第一次是系统向resolveInstanceMethod
发送消息,那第二次是什么时候调用的呢?后续我们进行分析。
我们如何让程序正常运行不崩溃呢?这里就讲到了动态添加方法。
#import "TPerson.h"
#import <objc/message.h>
@implementation TPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@" --- resolveInstanceMethod : %@ - %@",self, NSStringFromSelector(sel));
if (sel == @selector(formatPerson)) {
IMP imp = class_getMethodImplementation(self, @selector(getPerson));
Method method = class_getInstanceMethod(self, @selector(getPerson));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
@end
输出:
--- resolveInstanceMethod : TPerson - encodeWithOSLogCoder:options:maxLength:
<TPerson: 0x101551c00> - -[TPerson getPerson]
程序正常运行。resolveInstanceMethod
只调用了一次。因为动态添加了getPerson
方法。在lookUpImpOrForwardTryCache
获取调用 imp
。
流程:resolveMethod_locked
-> resolveInstanceMethod
-> resolveInstanceMethod
-> lookUpImpOrNilTryCache(inst, sel, cls)
-> lookUpImpOrForwardTryCache
-> imp
。
resolveClassMethod类方法动态决议
添加如下代码
@interface TPerson : NSObject
+ (void)Person;
@end
@implementation TPerson
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"resolveClassMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[TPerson person];
}
return 0;
}
输出:
resolveClassMethod :TPerson-person
resolveClassMethod :TPerson-person
+[TPerson person]: unrecognized selector sent to class 0x100008208
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[TPerson person]: unrecognized selector sent to class 0x100008208'
在崩溃之前 resolveClassMethod
也调用了两次。
动态添加方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(person)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("TPerson"), @selector(newPerson));
Method method = class_getInstanceMethod(objc_getMetaClass("TPerson"), @selector(newPerson));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("TPerson"), sel, imp, type);
}
NSLog(@"resolveClassMethod :%@ - %@",self,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
+ (void)newPerson {
NSLog(@"%@ - %s",self , __func__);
}
输出:
TPerson - +[TPerson newPerson]
发现 resolveInstanceMethod
和 resolveClassMethod
的流程基本一样。
整合实例对象、类对象的动态方法决议
#import "NSObject+TT.h"
#import <objc/message.h>
@implementation NSObject (TT)
- (void)getPerson{
NSLog(@"%@ - %s",self , __func__);
}
+ (void)newPerson{
NSLog(@"%@ - %s",self , __func__);
}
#pragma clang diagnostic push
// 让编译器忽略错误
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(formatPerson)) {
IMP imp = class_getMethodImplementation(self, @selector(getPerson));
Method method = class_getInstanceMethod(self, @selector(getPerson));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, imp, type);
} else if (sel == @selector(person)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("TPerson"), @selector(newPerson));
Method method = class_getInstanceMethod(objc_getMetaClass("TPerson"), @selector(newPerson));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("TPerson"), sel, imp, type);
}
return NO;
}
@end
在 TPerson
中引入,然后运行程序。发现程序能够正常运行。
补充:oop 与 aop
- oop:面向对象编程。什么人做什么事,分工明确。
- 优点:低耦合
- 缺点:冗余代码多,强耦合。
- aop:面向切面编程。oop 的延伸。
- 切点:切入的方法和类
- 优点:通过动态方法进行方法的注入。
- 缺点:性能消耗严重。