Objective-C的消息传递与转发
2018-12-27 16:21 法子 阅读(643) 评论(0) 编辑 收藏 举报注意:本文中”消息”和”方法”意思相同。
在读一些比较"秀"的代码时候,遇到声明了方法但没实现,父类也没实现,仍然可以正常运行。这是利用了Objective-C是消息型语言,通过重写系统函数,在运行时实现了消息的转发:
- (id)forwardingTargetForSelector:(SEL)aSelector
在函数里返回实现了方法的对象(参见下面讲解的第2.->(2)->①阶段)
如果不了解Objective-C的消息传递和转发,读这些代码就会难以理解。
Objective-C的消息传递与转发
Objective-C由消息型语言Smalltalk演化而来。在运行时才会去查找所要执行的方法;才会动态绑定,确定接受消息(即方法)的对象。而不是类似C语音的静态绑定,C语音不考虑内联的情况下,编译时期就已经决定了运行时所应调用的函数。
Objective-C虽然在底层都是C语音函数,不过运行期组件构成了一种与开发者所编代码相链接的动态库,负责调用哪个方法、谁接收这个方法。
当调用一个函数的时候,分为两个阶段:1.传递消息(一般我们写的正常函数,在消息传递时期就可以正确实现)。2.(如果消息传递失败)消息转发。
1.传递消息与objc_msgSend方法
比如我们调用一个函数
id returnValue = [someObject messageName: parameter];
编译器会把它转化为一个C语言函数:
// void objc_msgSend(id self, SEL cmd, ...) id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
该方法在someObject类中搜寻"方法列表",如果找不到,就沿继承体系依次向上在父类们中找,到顶层父类还找不到,就会进入第二步:消息转发。
如果找到,匹配结果会缓存到每个类的"快速映射表",以提高下一次执行相同方法的速度。
整个过程比较复杂,还有其他的相关优化技术。我们只需要了解工作原理,能读懂一些比较"秀"的代码就可以了。
另外,这个C函数在Objective-C中是可以调用的,不过首先要把BuildSettings->Enable strict checking of objc_msgSend Calls改为NO。有些程序员会在使用这个函数来去除"PerformSelector may cause a leak because its selector is unknown"的警告
#import <objc/message.h> - (id)execute:(SEL)selctor parameter:(id)parameter { // [self performSelector:selctor withObject:parameter];//会警告:PerformSelector may cause a leak because its selector is unknown id returnValue = objc_msgSend(self, selctor, parameter); return returnValue; }
还有,objc_msgSend是用来处理通常状况的,还有其他的类似函数。比如待发消息的返回值是结构体:objc_msgSend_stret,返回值是浮点数:objc_msgSend_fpret,要给父类发消息:objc_msgSendSuper等。
2.消息转发
分为两个阶段:(1)动态方法解析:征询接受者,看是否能动态添加方法。(2)消息转发机制:①让接收着看看有没有其他对象处理②完整的消息转发机制。
(1)动态方法解析
对象收到无法解析的方法,首先调用所属类的类方法,返回值表示这个类是否能新增一个实例方法来处理:
//尚未实现的方法是实例方法 + (BOOL)resolveInstanceMethod:(SEL)sel //尚未实现的方法是类方法 + (BOOL)resolveClassMethod:(SEL)sel
重写该函数可以在函数里为类添加新方法,此方案常用来实现@dynamic属性。要访问CoreData框架中NSManagedObjects对象的属性时就是这么做的:
#import <objc/runtime.h> //声明并实现 id autoDictionaryGetter(id self, SEL _cmd); //声明并实现 void autoDictionarySetter(id self, SEL _cmd, id value); + (BOOL)resolveInstanceMethod:(SEL)sel { NSString *selectorString = NSStringFromSelector(sel); if (/*selector is from a @dynamic property */) { if ([selectorString hasPrefix:@"set"]) { class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@"); } else { class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:"); } return YES; } return [super resolveInstanceMethod:sel]; }
(2)消息转发
如果没有做(1)的工作,就会进入第二阶段消息转发。消息转发也分为两个阶段。
①备援接受者
这一步中,系统会询问,能不能把消息转给其他接受者来处理。也就是文章开头举的例子
- (id)forwardingTargetForSelector:(SEL)aSelector { return _replacementReceiver; }
重写该方法,返回一个备援对象,来处理aSelector方法。
②完整的消息转发
如果上一步没处理,就会进入最后这一步,启用完整的消息转发机制。生成NSInvocation对象,存储了那条未处理的消息的全部细节:方法、目标、参数等。调用:
- (void)forwardInvocation:(NSInvocation *)anInvocation
如果目标子类没有重写实现该方法,它会层层向父类传递调用,直到传递给NSObject的该方法,里面会调用"doesNotRecognizeSelector:",然后抛出异常。
重写该函数,可以在里面做很多事,比如修改目标(像①修改备援接受者一样)、修改方法、修改参数等等。
现在回头看开头的问题,就很清楚了,是消息的处理走到了第2.->(2)->①阶段。