一、Class 结构

1.1 objc_class(类)
上图是 Class 的总体结构图,其中 OC 中的 Class 直接转换为 C++ 中的对应的结构体objc_class,objc_class中主要包含:
-
isa指针:对象的isa指向类,类的isa指向元类,元类的isa指向根元类,根元类的isa指向自身。 -
supreclass指针:指向父类。 -
cache方法缓存列表,下面第二小节会重点介绍。 -
bits包含着具体的类信息。
1.2 cache_t (方法缓存)

cache_t 主要用来缓存方法,内部使用散列表(也称哈希表)来缓存曾经调用过的方法,以SEL作为 key ,以IMP作为Value ,通过空间换时间的思想提高方法查找速度。每次调用方法前,首先去cache_t中查找通过散列表特性查找是否有该方法。存在则直接调用,否则去class_rw_t中的methods 遍历查找该方法,调用并将该方法缓存到cache_t中,以便提高下次调用效率。
1.3 class_rw_t (类信息)
bits & FAST_DATA_MASK 可获取类信息,对应的类型为class_rw_t(rw表示read、write,即可读可写)。
-
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的( ro 即read only),仅包含了类的初始内容,未包含分类的信息。当首次加载类的时候,会将class_rw_t中的方法列表加载到二维数组methods中,放在数组末尾,具体可以查看Category底层分析。 -
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容以及分类的内容。
1.4 method_t(方法结构体)
method_t是对方法或函数的封装。主要由三部分组成:
- IMP代表函数的具体实现
- SEL代表方法或函数名,也做选择器
- types包含了函数返回值以及参数编码的字符串,苹果官方文档搜索
Type Encoding可查看相关信息。如[self test]中,test 方法对应的types值为v16@0:8,v表示void,@表示id,:表示SEL, 因为OC方法调用底层会默认传两个参数,一个是id类型的self,一个是SEL类型的_cmd。另外,16 表示参数的总长度,0 和 8 分别表示第一个参数和第二个参数的偏移值。
iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码。如NSLog(@"%s",@encode(id)) 输出结果为@。
二、消息发送
OC 中的方法调用,其实都是转换为 objc_msgSend 函数的调用,该函数默认接收两个参数:一个是id类型的消息接收者,另一个是SEL类型的_cmd。objc_msgSend的执行流程可以分为三个阶段,如果三个阶段都没有找到合适的方法进行调用,就会报一个经典错误unrecognized selector sent to instance。
- 消息发送阶段
- 动态方法解析阶段
- 消息转发阶段

- 1、消息发送阶段首先判断消息接收者是否为 nil ,为 nil 直接退出;否则,去消息接收者的cache中寻找是否存在发方法。
- 2、消息接收者的cache中存在直接调用,不存在则去接收者的类信息
class_rw_t中查找,查找到放入到消息接收者的cache中;否则,去消息接收者父类cache中查找; - 3、消息接收者父类cache中存在该方法,直接调用;不存在则去接收者的父类类信息
class_rw_t中查找,查找到放入到消息接收者父类的cache中(从图中看应该是放入消息接受者的cache中,和此处矛盾);否则,去消息接收者父类的父类cache中查找;即循环执行第三步,也即上图红色框圈入的部分。 - 4、如果一直找到最基层的父类中,依然没有找到方法,则会进入动态方法解析阶段。
子类没有实现方法会调用父类的方法,并且将父类方法加入到子类自己的cache 里。?????
三、动态方法解析

如果可以找到方法的实现,就进入消息转发阶段;否则,调用+resolveInstanceMethod:和+resolveClassMethod:,在上述两个方法内动态添加方法,防止崩溃,之后返回YES,重新返回消息发送阶段;如果没有动态添加方法,直接返回YES 或 NO,依然无法找到方法实现,进而发生奔溃。注意:上述两个方法的返回值都是 BOOL 类型,返回值只是标记是否动态解析方法,无论返回 YES 还是 NO,对消息执行流程无任何影响。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
// 获取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (void)other{
NSLog(@"%s", __func__);
}
四、消息转发阶段

所谓的消息转发是指:自身无法处理消息,转发给他人来处理。当方法解析阶段找到对应方法实现,就会来到消息转发阶段。消息转发阶段有三次拦截消息的机会,即下面三个方法,三个方法都有对应的对象方法和类方法。
forwardingTargetForSelectormethodSignatureForSelectorforwardInvocation
4.1 forwardingTargetForSelector(返回方法处理者)
@interface Person : NSObject
- (void)test;
@end
@implementation Person
//如果不实现该方法默认返回nil,则会进一步调用`methodSignatureForSelector `方法;否则,由该方法返回的对象处理消息。
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
//如果返回nil,则直接调用methodSignatureForSelector方法
return [[Cat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
Person *person = [[Person alloc] init];
[person test];
如果不实现forwardingTargetForSelector 方法默认返回nil 或直接在该方法中返回 nil ,则会进一步调用methodSignatureForSelector方法;否则,由该方法返回的对象处理消息,即传入返回的对象,直接调用objc_msgSend(返回值, SEL)方法。上面的Person类中没有test方法的实现,forwardingTargetForSelector: 方法中返回 [[Cat alloc] init] 对象处理。
4.2 methodSignatureForSelector(返回方法签名)
如果方法methodSignatureForSelector返回的方法签名为 nil,则直接崩溃;如果返回不为 nil ,则说明想处理消息,则进一步调用forwardInvocation方法。返回的方法签名会传递到forwardInvocation方法中的NSInvocation对象中。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
//这里的方法参数、返回值和`forwardInvocation`中的NSInvocation对应
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
4.3 forwardInvocation(方法调用)
当来到该方法后,可以在这里做任何事情。其中传入的参数anInvocation对象封装的是一个方法的调用,包含了方法调用者(anInvocation.target)、方法名(anInvocation.selector)、方法参数([anInvocation getArgument:NULL atIndex:0])。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"1123");
}
4.4 doesNotRecognizeSelector,发生崩溃
如果在为消息解析阶段找不到方法实现,且上述三个方法没有做任何处理,会直接调用doesNotRecognizeSelector方法 ,最终发生崩溃。
4.5 消息转发实际开发应用(防止找不到方法崩溃)
消息转发机制笔者在实际开发中有用到,主要是用来处理方法找不到,发生崩溃问题。思路主要是在+load方法中,交叉替换forwardingTargetForSelector方法,动态创建新的方法处理对象,并在该对象中动态添加方法。具体实现如下:
@implementation NSObject (Exception)
+ (void)load {
//防止外部手动调用load方法,固load方法中最好都要写上dispatch_once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
[objc_getClass("NSObject") swizzleMethod:@selector(forwardingTargetForSelector:) swizzledSelector:@selector(replace_forwardingTargetForSelector:)];
}
});
}
- (id)replace_forwardingTargetForSelector:(SEL)aSelector{
NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
if ([self respondsToSelector:aSelector] || signature) {
return [self replace_forwardingTargetForSelector:aSelector];
}
return [NSObject createFakeForwardTargetObject:self selector:aSelector];
}
+ (id)createFakeForwardTargetObject:(id)aTarget selector:(SEL)aSelector{
if ([[NSString string] respondsToSelector:aSelector]) {
NSString *szTarget = nil;
if ([aTarget isKindOfClass:[NSNumber class]]) {
szTarget = [NSString stringWithFormat:@"%@", aTarget];
}
if (szTarget) {
return szTarget;
}
}
FakeForwardTargetObject *fakeTaget = [[FakeForwardTargetObject alloc] initWithSelector:aSelector];
return fakeTaget;
}
@end
@implementation FakeForwardTargetObject
- (instancetype)initWithSelector:(SEL)aSelector{
if (self = [super init]) {
if(class_addMethod([self class], aSelector, (IMP)fakeIMP, NULL)) {
MCLog(@"add Fake Selector:[instance %@]", NSStringFromSelector(aSelector));
NSString *string = [NSString stringWithFormat:@"[%s:%d行]",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],__LINE__];
showExceptionAlert(string);
}
}
return self;
}
@end
id fakeIMP(id sender,SEL sel,...){
return nil;
}