我们在处理问题时有时会觉得假设多继承则问题处理可能会更方便一些,但是Objective-C又不支持多继承,所以我们可以自己实现多继承来处理问题,而Apple也给开发者提供了很方便的途径自行实现多继承。当然实现多继承的方式还有很多种,比如利用Delegate或者Categroy都可以实现多继承的形式,本文只探讨利用Runtime的消息转发机制来实现多继承。

首先说一些前序,当对象调用方法时,在对象的类继承体系中没有找到方法时,会发生下图所示


resolveInstanceMethod:是实现动态解析方法,这里不多言。forwardingTargetForSelector:是Fast Forwarding,系统就会在运行时调用这个方法,如果这个方法返回的是nil,则进入Normal Fowarding,即forwardInvocation:方法。

下面,以一个儿子继承父亲和母亲的例子来解释如何实现多继承。

/* ---------------------- Father.h ---------------------- */
#import <Foundation/Foundation.h>
@interface Father : NSObject

- (void)work;
@end

/* ---------------------- Father.m ---------------------- */
#import "Father.h"
@implementation Father

- (void)work {
    NSLog(@"I'm working.");
}
@end

/* ---------------------- Mother.h ---------------------- */
#import <Foundation/Foundation.h>
@interface Mother : NSObject

- (void)cook;
@end

/* ---------------------- Mother.m ---------------------- */
#import "Mother.h"
@implementation Mother

- (void)cook {
    NSLog(@"I'm cooking.");
}
@end

/* ---------------------- Son.m ---------------------- */
#import <Foundation/Foundation.h>
#import "Father.h"
@interface Son : Father
//利用OC的继承机制,这里单方面地先继承的Father类
@end

/* ---------------------- Son.m ---------------------- */
#import "Son.h"
@implementation Son
@end

1. Fast Forwarding

Apple提供了Fast Forwarding的方法:- (id)forwardingTargetForSelector:(SEL)aSelector; ,这个其实也是消息转发的一种,只是Apple提供了一种更为简便的方式进行消息转发,为了区分,这里标为Fast Forwarding。我们可以利用这个方法对消息进行重定向,在对象层面表现为继承的形式。当一个对象无法处理消息时,系统会调用这个方法,可以利用这个方法将消息的接受者替换为其他对象。

由上面的例子可以看到,我们利用OC的继承机制,首先单方面地继承Father类,我们想要让Son能够继承Mother,也就是说,Son的对象可以想Mother的对象一样处理消息。代码如下:

/* ---------------------- Son.h ---------------------- */
#import "Mother.h"
...
/* ---------------------- Son.m ---------------------- */
#import "Son.h"
@interface Son()
@property (nonatomic, strong) Mother *mother;
@end
@implementation Son

- (instancetype)init {
    self = [super init];
    if (self != nil) {
        _mother = [[Mother alloc]init];
    }
    return  self;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([_mother respondsToSelector:aSelector]) {
        return _mother;
    }
    return nil;
}
@end

在这里,我们在Son.m里添加了一个私有属性mother并将其初始化。然后我们重载了- (id)forwardingTargetForSelector:(SEL)aSelector;方法,当son无法处理消息时,系统会调用这个方法,我们首先判断_mother是否能够处理该消息,如果可以的话,将返回_mother。
测试代码如下:

#import <Foundation/Foundation.h>
#import "Son.h"
int main(int argc, const char * argv[]) {
    id son = [[Son alloc] init];
    [son work];
    [son cook];
    // [(Mother *)son cook];
    return 0;
}
/* 运行结果:
 * I'm working.
 * I'm cooking.
 */

这里有个小细节,
对于编译器而言,son是无法处理cook方法的,有三种处理办法
1. 可以将其强制转换为Mother类型即可通过编译器
2. 可以在Son里面声明cook方法,但是不作实现
3. 将son直接生成为id类型,这样为了避免编译器报错,还需要导入Mother类,简便起见,我们可以直接在Son.h里直接导入Mother.h,这样只需要导入Son类即可

这里有个小细节,如果在这个方法里返回self,会发生什么呢?
我们通过(void)instrumentObjcMessageSends(YES);可以在/tmp/msgSends-XXX查看消息调用机制,(如果是iOS则需要声明extern void instrumentObjcMessageSends(BOOL);
可以看到消息的

+ Son NSObject resolveInstanceMethod:
+ Son NSObject resolveInstanceMethod:

- Son Son forwardingTargetForSelector:
- Son Son forwardingTargetForSelector:
- Son NSObject methodSignatureForSelector:
- Son NSObject methodSignatureForSelector:
- Son NSObject class
- Son NSObject doesNotRecognizeSelector:
- Son NSObject doesNotRecognizeSelector:

我们发现返回self并不会让重启消息转发机制,而是直接进入了标准消息转发。也就是说返回self和返回nil效果是一样的,在forwardingTargetForSelector:里面进行动态添加方法然后返回self企图重启消息发送是不行的,动态添加方式只能在resolveInstanceMethod里面进行

2. Normal Forwarding

我们还可以利用
- (void)forwardInvocation:(NSInvocation *)anInvocation;
来进行标准的消息转发,值得注意的是,在调用这个方法之前,系统会先调用
- (id)forwardingTargetForSelector:(SEL)aSelector;
这个方法,如果这个方法返回为nil,才会调动标准的消息转发机制,如果该方法返回不为nil,则不会触发标准消息转发机制。

我们注意一下这里有个anInvocation参数,在系统向对象发送forwardInvocation:消息前,会首先调用methodSignatureForSelector:方法,取得返回的方法签名,然后生成anInvocation。所以我们重写forwardInvocation:的同时,还需要重写methodSignatureForSelector:方法。另外,这里生成了anInvocation相比于Fast Forwarding速度较慢一些。

这里我们也引入在Apple官方文档中使用的图(略作修改),当son接收到cook的消息时,son无法处理这条消息,但是son的背后还有一个mother呀,于是当系统调用son的forwardInvocation方法时,我们在forwardInvocation方法中对其进行处理,将该条消息发送给Mother的一个实例就可以了,这样就完成了消息的转发。自己完成不了的事,交给老妈就好了。当然,从表面上看,还是由son处理了这条消息。

/* ---------------------- Son.h ---------------------- */
#import "Mother.h"
...
/* ---------------------- Son.m ---------------------- */
#import "Son.h"
@interface Son()
@property (nonatomic, strong) Mother *mother;
@end
@implementation Son

- (instancetype)init {
    self = [super init];
    if (self != nil) {
        _mother = [[Mother alloc]init];
    }
    return  self;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    if (methodSignature != nil) return methodSignature;    
    if ([_mother respondsToSelector:aSelector]) {
        return [_mother methodSignatureForSelector:aSelector];
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([_mother respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:_mother];
    }
}
@end

运行代码:

#import <Foundation/Foundation.h>
#import "Son.h"
int main(int argc, const char * argv[]) {
    id son = [[Son alloc] init];
    [son work];
    [son cook];
    // [(Mother *)son cook];
    return 0;
}
/* 运行结果:
 * I'm working.
 * I'm cooking.
 */

可以看到,我们也一样成功的让son处理了“继承”自Mother的cook消息。

快速转发的区别:

在方法1.快速转发中,可以看到,我们简单的如果_mother可以处理该消息,就返回了_mother对象,然后系统就将cook这条消息转发给了_mother,在标准的消息转发则比较复杂,但是标准的消息转发有更好的消息可控性,譬如,我们有两个对象都可以完成这条消息的处理,我们希望同时将消息转发给两个对象,此时重定向是无法完成的,而在标准的消息转发中我们可以通过多次调用invokeWithTarget:方法,来将该消息同时转发给两个或者多个对象。

值得注意的是,消息转发虽然很像继承,但是编译器并不会将其混淆。

NSLog(@"%@",[son isKindOfClass:[Father class]]?@"YES":@"NO");
    NSLog(@"%@",[son respondsToSelector:@selector(work)]?@"YES":@"NO");

    NSLog(@"%@",[son isKindOfClass:[Mother class]]?@"YES":@"NO");
    NSLog(@"%@",[son respondsToSelector:@selector(cook)]?@"YES":@"NO");
/* 运行结果:
 * YES
 * YES
 * NO
 * NO
 */

很明显,在编译器看来,son很明显是一种Father,也能够响应work消息,但是不是一种Mother,也不能响应cook消息。如果真的想以假乱真,我们还需要重写respondsToSelector:方法和isKindOfClass:,此外,还有一个类方法instancesRespondToSelector:

/* ---------------------- Son.m ---------------------- */
...

- (BOOL)isKindOfClass:(Class)aClass {
    if ([super isKindOfClass:aClass]) {
        return YES;
    } else if(aClass == [Mother class]) {
        return YES;
    }
    return NO;
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ([super respondsToSelector:aSelector]) {
        return YES;
    } else if([_mother respondsToSelector:aSelector]) {
        return YES;
    }
    return NO;
}
+ (BOOL)instancesRespondToSelector:(SEL)aSelector {
    if ([super instancesRespondToSelector:aSelector]) {
        return YES;
    } else if([Mother instancesRespondToSelector:aSelector]) {
        return YES;
    }
    return NO;
}
...

ps. 消息转发还可以做很多有用的事情,比如也可以完成每个成功的男人身后都有一个出色的女人这样的事情。