OC,objc_msgSend()函数做了哪些事

OC,objc_msgSend()

简介:用一句话简介消息传递那就是:用一个 C语言函数,向一个实例传递一个字符串,实例拿到字符串后与自己的method_list中的SEL比较,遇到一样的就找到对应IMP执行。SEL就是一个字符串,IMP就是一个函数指针,在一个Method结构体中封装着这两者,因此他俩一一对应。

  • 在向一个实例发送消息时[obj doSomthing];实际上是调用的这个函数objc_msgSend(obj,@selector(doSomething));(该函数在中)
  • 接下来将从头到尾表述在这个函数中究竟干了多少事情。
  • 阶段1(对于能独立解决的问题):
  1. 寻找类:根据上一篇的OC是如何使C语言变得面向对象的,知道一个objc_object结构体中就只有isa(一个指向objc_class的指针),因此需要从他的objc_class结构体中查找函数,于是顺着isa指针找到了这个objc_class。
  2. 访问cache:objc_cache结构体中装着一个method结构体的数组,先在这个数组中遍历method,对于每一个method,要对照method中的SEL与上面传递进来的@selector(doSomething)是不是一样,如果一样就确认是需要调用它的IMP。
  3. 访问method_list:如果在步骤2中没有找到一样的SEL,就需要遍历所有的函数了,与遍历cache时一样,对照SEL,选取IMP。如果在这个步骤中仍然不能找到对应的SEL,那么就会进入阶段2(不能独立解决的问题)。
  4. 假如通过上述1-3过程找到了method,之后执行对应的IMP就可以了。
  • 阶段2(对于不能独立解决的问题):
  1. 寻找父类:在阶段1中,当在cache和method_list中不能找到一致的SEL时,msgSend函数会继续往祖坟上刨,它根据objc_class中的super指针(指向父类的objc_class结构体的指针)找到该类的父类,然后在父类的cache和method_list中执行阶段1的2、3步一样的操作。
  2. 寻找父类的父类:如果在这个时候还是不能找到对应的SEL,就会继续根据super指针继续寻找父类,直到super指针是nil,说明没有一个人可以响应这条消息。这个时候如果没有一些阶段3的防护措施就会报错了,通常是unrecognized selector的错误。
  • 阶段3(所有子类和父类都不能解决的问题):
  • 对于我们没有预测到的一些错误调用(使用performSelector传入一个随便的selector)或者在实现上的一些失误(比如你答应了某个协议去实现他的一些方法,然后在implement中没有写对应的实现,而协议的另一端正在调用协议中的方法)。有三种补救方式允许我们避免程序的崩溃:在resolve(Instance/Class)Method中做检查、提供另外一个可以供转发的对象、使用NSInvocation重新调用。
  1. 在resolveMethod中做检查(这里均以实例方法为例):首先进入resolveInstanceMethod函数,注意这个方法不会影响阶段1和阶段2的所有过程,也就是说,执行了这个方法之后,如果仍然找不到对应的SEL,依然是会报错的。因此我们可以在这个方法中,动态地添加其实现。创建TestObj类如下,啥属性啥方法都没有,却能向他发送任何实例消息。原因就是,在resolveInstanceMethod方法中接收到一个找不到的SEL,不论这个SEL叫什么,我们都给它一个默认的实现defaultDealMethod。因此,TestObj永远都不会引起崩溃。

#import 

@interface TestObj : NSObject

@end

#import "TestObj.h"
#import 

@implementation TestObj

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *str = NSStringFromSelector(sel);
    NSLog(@"TestObj没有找到方法:%@,来到了resolveMethod中",str);
    class_addMethod(self, sel, (IMP)defaultDealMethod, "v@:");
    return YES;
}

void defaultDealMethod(id self, SEL sel){
    NSString *str = NSStringFromSelector(sel);
    NSLog(@"DefaultDealMethod:%@",str);
}
@end
  1. 第二个保障,就是为这个不能处理的对象找一个可以求助的实例。为了告知这个实例是谁,需要重写方法forwardingTargetForSelector:(SEL)sel方法。既然刚刚有一个无所不能的TestObj方法,那就让他来担当这个求助对象吧,虽然他没做什么实质工作,但能保证不崩溃。我们在测试的ViewController中这样写,虽然随便调用了两个没有实现的方法,但是并没有崩溃,因为他成功向TestObj求助了,而TestObj为了帮助它,在自己的方法列表中加入了两个新的方法,这样就可以查找到SEL并成功执行。
- (void)viewDidLoad {
    [super viewDidLoad];
    objc_msgSend(self, @selector(asdasdasdadsas));
    objc_msgSend(self, @selector(asdakjsdajdjka));
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return [[TestObj alloc] init];
}

3.第三个保障:使用Invocation调用(个人不大明白这样做的必要性),Invocation类似设计模式中的命令模式,就是将一个命令或者是调用封装为一个Obj,Invocation中大体包含如下属性:一个MethodSignature(格式)、target(目标,也就是消息发送的对象)、selector(方法名);一个MethodSignature中又大体包含如下属性:参数个数、方法长度、返回值类型、返回值长度。由此可见,一个Invocation就是对一次调用函数各方面格式、目标的封装。因此我们需要做的就是在最后一条保障中,规定一个Invocation,并使用这个Invocation完成调用。

- (id)forwardingTargetForSelector:(SEL)aSelector{
    //return [[TestObj alloc] init];
    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[TestObj alloc] init]];
}
  • Signature是Invocation的组成元素,因此在得到Invocation前需获取到这些格式信息,对于这个格式:第一位是返回值,可以是c(char)、i(int)、s(short)、l(long)等等。第二位是接收的第一个参数self,因此是"@",第三位是selector用":"表示,之后的位置就都是自定义的参数了,这些类型在官网上可以查到,后面给出一些常用的。举个例子,这样一个函数:
- (instancetype)initWithName:(NSString *)name height:(float)height weight:(float)weight father:(id)father mother:(id)mother;

  • 它的格式就可以这样表示"@@:@ff@@";
  • 每一位分别对应 instance返回值、默认参数self、默认参数SEL、参数name、参数height、参数weight、参数father、参数mother。
char:c
int:i
short:s
long:l
longlong:q
//上面这些都大写就代表是 unsigned的,如 unsigned int:I
float:f
double:d
bool:B
void:v
id:@
SEL: :
  • 如果我们在methodSignature中返回了一个非nil的signature,系统就会为我们创建一个invocation并调用forwardingInvocation方法,在该方法中我们会得到之前预定好的格式的invocation,我们就可以invoke这个invocation了。

  • 当然,如果最后第三个保障也没有好好利用的话就只有崩溃了。

另外,如果xcode不允许使用带参数的msgSend()函数,在项目的buildSettings中搜索strict,有一个Enable strict checking of objc_msgSend call选项,把他置为NO,之后就好用了。

以上就是OC消息传递的全部过程

结束

你可能感兴趣的:(OC,objc_msgSend()函数做了哪些事)