Runtime
[obj message]调用执行过程如下图:
1456815016258663.png
一个函数是由一个selector(SEL),和一个implement(IML)组成的。Selector相当于门牌号,而Implement才是真正的住户(函数实现)。
一个OC对象执行[obj message]方法后,如果message并没有被obj所属类实现,会报unrecognized selector sent to instance
错误,runtime给了我们3次机会去避免发生这样的情况。
1.OC对象在收到无法解读的消息后,首先会调用所属类的+ (BOOL)resolveInstanceMethod:(SEL)sel
,这个方法在运行时,没有找到SEL的IML时就会执行。这个函数是给实例对象利用class_addMethod添加函数的机会。根据文档,如果实现了添加函数代码则返回YES,未实现返回NO。resolveClassMethod:
是用于动态解析一个类方法;而resolveInstanceMethod:
是用于动态解析一个实例方法。
//类函数
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}```
关于class_addMethod这个方法,是这样定义的
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
cls 在这个类中添加方法,也就是方法所添加的类
name 方法名,这个可以随便起的
imp 实现这个方法的函数
types 定义该数返回值类型和参数类型的字符串,这里比如"v@:",其中v就是void,带表返回类型就是空,@代表参数,这里指的是id(self),这里:指的是方法SEL(_cmd),比如再定义一个函数
int newMethod (id self, SEL _cmd, NSString *str) {
return 100;
}
那么添加这个函数的方法就应该是ass_addMethod([self class], @selector(newMethod), (IMP)newMethod, "i@:@");
2.如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中没有找到或者添加方法,消息继续往下传递到`- (id)forwardingTargetForSelector:(SEL)aSelector`看看是不是有对象可以执行这个方法,该方法返回未被接收消息最先被转发到的对象。如果一个对象实现了这个方法,并返回一个非空的对象(且非对象本身),则这个被返回的对象成为消息的新接收者。另外如果在非根类里面实现这个方法,如果对于给定的selector,我们没有可用的对象可以返回,则应该调用父类的方法实现,并返回其结果。
//将消息转出某对象
-
(id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"MyTestObject _cmd: %@", NSStringFromSelector(_cmd));NoneClass *none = [[NoneClass alloc] init];
if ([none respondsToSelector: aSelector]) {
return none;
}return [super forwardingTargetForSelector: aSelector];
}
3.当前面两步都无法处理消息时,运行时系统便会给接收者最后一个机会,将其转发给其它代理对象来处理。这主要是通过创建一个表示消息的`NSInvocation`对象并将这个对象当作参数传递给`forwardInvocation:`方法。我们在`forwardInvocation:`方法中可以选择将消息转发给其它对象。
在这个方法中,主要是需要做两件事:
(1).找到一个能处理anInvocation调用的对象。
(2).将消息以anInvocation的形式发送给对象。anInvocation将维护调用的结果,而运行时则会将这个结果返回给消息的原始发送者。
真正执行从 `methodSignatureForSelector:`返回的NSMethodSignature。这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行。在这个函数里可以将NSInvocation多次转发到多个对象中,这也是这种方式灵活的地方。(forwardingTargetForSelector只能以Selector的形式转向一个对象)
-
(NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:@"set"].location == 0) {
//动态造一个 setter函数
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
} else {
//动态造一个 getter函数
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
} -
(void)forwardInvocation:(NSInvocation *)invocation
{
//拿到函数名
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:@"set"].location == 0) {
//setter函数形如 setXXX: 拆掉 set和冒号
key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
NSString *obj;
//从参数列表中找到值
[invocation getArgument:&obj atIndex:2];
[data setObject:obj forKey:key];
} else {
//getter函数就相对简单了,直接把函数名做 key就好了。
NSString *obj = [data objectForKey:key];
[invocation setReturnValue:&obj];
}
}
`- (void)doesNotRecognizeSelector:(SEL)aSelector`
作为找不到函数实现的最后一步,NSObject实现这个函数只有一个功能,就是抛出异常。
虽然理论上可以重载这个函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出“一定不能让这个函数就这么结束掉,必须抛出异常”。
#使用场景:
在一个函数找不到时,Objective-C提供了三种方式去补救:
1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数
2、调用forwardingTargetForSelector让别的对象去执行这个函数
3、调用methodSignatureForSelector(函数符号制造器)和forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。
如果都不中,调用doesNotRecognizeSelector抛出异常。
参考:
http://www.cocoachina.com/ios/20160302/15494.html
http://www.cnblogs.com/biosli/p/NSObject_inherit_2.html
http://www.cocoachina.com/ios/20150205/11113.html