前言
其他编程语言所说的函数调用,在oc中被称作为发送消息;消息转发的作用,开发者可以在找不到的方法的情况下,可以通过动态添加方法或者是消息转发,确定本次发送消息是否成功,通过这样的特性开发者可以做很多必要的善后处理。
ios消息转发的作用;
(1)对象发送了未实现的消息,可以通过消息转发机制,转移到其他对象去处理该消息;
(2)除了协议、类别,也可以通过消息转发机制实现多继承;
实现ios消息转发前的准备工作;
(1)为了配合下面的实例预先定义好SleepViewController,实现sleep和eat方法;
#import "SleepViewController.h"
@interface SleepViewController ()
@end
@implementation SleepViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (void)sleep
{
NSLog(@"excute SleepViewController sleep");
}
- (void)eat
{
NSLog(@"ok,begin eat");
}
@end
ios消息转发的过程;
(1)动态方法解析,比如执行[self performSelector:@selector(name)];在本类父类以及NSObject都未能查找到该方法,就是先判断是否需要动态添加该方法,本例运用runtime动态添加了testClassName方法,执行结果会输出"add testClassName method";
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(name)) {
class_addMethod(self, sel, class_getMethodImplementation([self class], @selector(testClassName:)), method_getTypeEncoding(class_getInstanceMethod([self class], @selector(testClassName:))));
return YES;
}else if (sel == @selector(sleep)){
return NO;
}
return [super resolveInstanceMethod:sel];
}
- (void)testClassName:(NSString *)string
{
NSLog(@"add testClassName method");
}
(2)如果(BOOL)resolveInstanceMethod:(SEL)sel或者(BOOL)resolveClassMethod:(SEL)sel返回NO,消息转发就会进入转发机制;比如执行[self performSelector:@selector(sleep)],这个sleep方法在SleepViewController声明并实现,结果输出"excute SleepViewController sleep";
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"not found method sleep");
if (aSelector == @selector(sleep)) {
return [[SleepViewController alloc] init];
}else{
NSLog(@"not found eat");
return nil;
}
}
(3)当重定向还不作处理的时候,这消息转发机制就会出发,这个时候就会调用- (void)forwardInvocation:(NSInvocation *)anInvocation这个方法;但是在这个方法执行之前会先调用methodSignatureForSelector方法,所以我们也要复写这个方法,否则就会报异常;所以我们要重写这个方法。比如执行[self performSelector:@selector(eat)];既没有动态添加该方法,也没有重定向该方法,所以执行了一下的两个方法,做最后的逻辑处理;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"method name -- %@",NSStringFromSelector(aSelector));
if (aSelector == @selector(eat)) {
//签名方法;
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sele = [anInvocation selector];
SleepViewController *slee = [[SleepViewController alloc] init];
if ([slee respondsToSelector:sele]) {
//转发到SleepViewController执行eat方法;
[anInvocation invokeWithTarget:slee];
}else{
[super forwardInvocation:anInvocation];
}
}
解释:在没有动态添加方法情况下,第(2)(3)步,都是将处理消息的操作,转移给了其他的对象。区别就是在第(2)步转发,只能转发一个指定的对象;在第(3)步的话,可以转发多个对象。
好玩的东西:利用oc消息转发,实现多重delegate代理;
大家都比较清楚,在通常情况下,delegate只能对象之间是一对一通信的,通过上述的消息转发的分析,在转发的第(3)步,可以实现多重代理,即多个委托对象。我先上一段代码,后面再解读代码逻辑;
(1)多重代理的处理类,保存多个委托对象,通过消息转发将要执行委托函数转发至其他委托对象(记住本类不实现任何委托相关的逻辑);
#import "MultipleDelegateProxy.h"
@interface MultipleDelegateProxy ()
@property (nonatomic,strong) NSPointerArray *weakRefTargets;
@end
@implementation MultipleDelegateProxy
- (void)setDelegateTargets:(NSArray *)delegateTargets {
self.weakRefTargets = [NSPointerArray weakObjectsPointerArray];
for (id delegate in delegateTargets) {
[self.weakRefTargets addPointer:(__bridge void *)delegate];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector{
if ([super respondsToSelector:aSelector]) {
return YES;
}
for (id target in self.weakRefTargets) {
if ([target respondsToSelector:aSelector]) {
return YES;
}
}
return NO;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
for (id target in self.weakRefTargets) {
if ((sig = [target methodSignatureForSelector:aSelector])) {
break;
}
}
}
return sig;
}
//转发方法调用给所有delegate
- (void)forwardInvocation:(NSInvocation *)anInvocation{
for (id target in self.weakRefTargets) {
if ([target respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:target];
}
}
}
@end
(2)使用MultipleDelegateProxy,multipleDelegate作为scrollview的委托对象,multipleDelegate的delegateTargets添加了两个委托对象self和scrollDelegate,在scrollview滚动的过程中会通过MultipleDelegateProxy类中重写的- (BOOL)respondsToSelector:(SEL)aSelector预先判断multipleDelegate是否实现了相应的委托方法(这里肯定要返回yes的,不然就不会出现消息转发),找不到委托方法,最后将消息转发至self和scrollDelegate进行处理,实现多重委托。
@interface MemberCenterViewController () <UIScrollViewDelegate> {
MultipleDelegateProxy *multipleDelegate;
ScrollDelegate *scrollDelegate;
}
@property (nonatomic, strong) UIScrollView *scroll;
@end
@implementation MemberCenterViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.scroll = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scroll.backgroundColor = [UIColor lightGrayColor];
self.scroll.contentSize = CGSizeMake(375, 800);
multipleDelegate = [MultipleDelegateProxy new];
scrollDelegate = [ScrollDelegate new];
multipleDelegate.delegateTargets = @[self,scrollDelegate];
_scroll.delegate = multipleDelegate;
[self.view addSubview:_scroll];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(@"%@",self);
}
@end
在这里我解释一下为什么要重写MultipleDelegateProxy类中的- (BOOL)respondsToSelector:(SEL)aSelector方法?原因很简单,在设置scrollview的delegate属性后,会判断delegate是否实现了相应的委托方法,若没有实现的话,是不会执行这个委托方法(可选的)的;所以我们需要重写,逻辑是通过真正会执行委托的两个委托对象去判断是否实现的委托方法。
总结
通过消息转发机制,你可以看到oc这门语言的独特之处,增加了很多灵活性。可以使用它来做一些特殊值的判断处理,比如null值等,减少我们在接收后台返回数据时返回null对我们处理逻辑的影响。当然消息转发的作用不止如此,更多好玩的东西,等待着我们去发掘。