runtime可以帮助我们实现一些oc层的api达不到的功能。那就先需要了解一下。
一、消息转发
oc中的动态特性,就是他在运行的时候,才能确定某些东西。比如实现方法这个过程。实际上是一个发送消息的过程。这个消息,也许是由接受者执行,也可能是由开发者设定的其他对象执行。消息与方法的绑定,也是运行时才确定.
消息的发送过程是这样的:
1.通过对象的isa指针找到它的类
2.在类的method list 中找到这个方法(根据SEL来找,方法的唯一辨识)
3.如果class中没有这个方法,就沿着这个类的isa指针 指向它的superclass去找
4.一旦找到,则执行这个方法的IMP(函数指针,指向方法执行的首地址)
但是,如果每次都要这么找的话,没有必要每次都在methodlist中循环查找,所以,一旦找到了这个方法,就存为class_cache,下一次就先在这里找,所以这一步应在执行在上文步骤1的前面。
如果找不到,而开发者没有做任何处理。那么自然会出现 unrecognized selector sent to instance的错误
其实在这个异常出现之前,会有三个步骤来转发这个消息,如果我们不做任何一个动作,则会异常。
1.动态的给这个消息添加一个实现方法
对象在接收到未知的消息时,会调用
+ (BOOL)resolveInstanceMethod:(SEL)sel ;如果是类方法,则是+ (BOOL)resolveClassMethod:(SEL)sel
在该方法中,我们可以将实现了的方法添加到这个消息里面
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(w)) {
class_addMethod([self class], sel, (IMP)w, "v@:@");
//找到了方法然后添加了实现,返回yes。
return YES;
}
return [super resolveInstanceMethod:sel];
}
void w(id self,SEL _cmd, NSString * name){
NSLog(@"%@",name);
}
这里需要理解一下class_addMethod
先看一下官方文档怎么说
首先,
第一个参数,是要添加的类,自然要传[self class]
第二个参数 name,是方法的名称,也就是SEL的类型,用@selector可以获取
第三个参数imp,也就是要给这个方法添加的实现函数,文档里面说了,这个函数必须要接受两个参数,一个是self,一个是_cmd
第四个参数:types,对imp的描述。至少有三个参数:第一个是返回类型,第二个是self,第三个是sel类型的_cmd,列子中用的是“v @:@”v代表,返回类型void
,@代表idleix,:代表sel类型,第二个@代表nsstring类型。
这里面的转换类型,可以在官方文档中搜Type Encodings查找
2,如果在上一步没有做任何处理,runtime会执行这个方法,尝试把这个消息发给另一个对象来执行。
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(setBackgroundColor:)) {
return label;
}
return [super forwardingTargetForSelector:aSelector];
}
这一步只合适于我们将消息转发到另一个能处理该消息的对象,但是不能对该消息做任何处理
3,如果上一步还不能处理,则会走这个方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
//抛出异常之前,会给对象发送这个消息forwardInvocation
//anInvocation这个参数包括selector,目标(target)和参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
//抛出异常之前,会给对象发送这个消息forwardInvocation
这个参数包装了原始消息和对应的参数
SEL sel = anInvocation.selector;
if ([label respondsToSelector:sel]) {
[anInvocation invokeWithTarget:label];把消息转发给label
}else {
[self doesNotRecognizeSelector:sel];
}
}
用这个方法的时候,我们必须要重写另一个方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
因为上一个方法中的参数使用从这个方法创建的NSInvocation对象,所以必须要重写方法。
- (NSMethodSignature *)methodSignatureForSelector: (SEL)aSelector {
NSMethodSignature *s = [super methodSignatureForSelector: aSelector];
if (!s ) {
s = [label methodSignatureForSelector:aSelector];
}
return s;
}
这里是需要重新给method签名,method里面包三个对象:一个sel,一个method_types,一个是imp,sel是根据方法名和参数类型生成唯一识别,method_type包括了消息的所有参数类型(包括两个隐藏参数:一个self,一个_cmd也就是SEL类型),imp就是函数指针。
消息转发的过程就是这样的。
下面介绍一下上面提到的SEL,IMP,Method
SEL
SEL是表示一个方法的selector指针,oc会根据不同方法的名字,参数序列,生成唯一的标识,也就是这个指针的地址,所以就不难理解为什么oc中不能定义参数不同,但是方法名相同的方法了。
所以我们现在知道了,SEL是一个指向方法的指针。 对于我们而言,也就是这个方法的函数名(唯一辨识);
我们可以通过@selector()或者NSSelectorFromString()得到这个指针;
IMP
上文提到,IMP是一个函数指针,指向方法实现的首地址。通过SEL能够找到对应的IMP。
Method
Method表示方法,包含
一个SEL
一个 method_types
一个 IMP
这里就相当于SEL和IMP之间有了映射。
所以现在不难了解开头说的,消息与方法的绑定(objc_msgSend(receiver,selector,参数···)),也是运行时才确定
1,首先找到这个selector对应的方法实现。因为不同的类中对同一方法有不同的实现,需要根据接受者找到确切的实现。
2,找到了SEL之后,找到IMP,然后将接受者对象本身,以及SEL本身和其他参数传给他
3,将IMP的返回作为这个绑定操作的返回。
所以也不难理解,为什么[self class]和[super class]会打印出self这个子类。因为在super中调用的这个class方法,传入的第一个参数self是子类。