OC不像C++一样支持多重继承,当我们需要从多个父类汲取属性和方法时,需要用到一些特殊的操作。

1. 组合

在ClassC的.h或者.m文件中,声明ClassA和ClassB的实例

@class ClassA,ClassB;

@interface ClassC : NSObject

@property (nonatomic,strong) ClassA *a;
@property (nonatomic,strong) ClassB *b;
- (void)sport;

@end
#import "ClassC.h"
#import "ClassA.h"
#import "ClassB.h"

@implementation ClassC
- (void)sport{
    [self.a run];
    [self.b walk];
}
@end

2. 协议

将C类需要继承的方法以及属性在ClassA和ClassB中各自声明一份协议,C类遵守这两份协议,同时在C类中实现协议中的方法以及属性。

@protocol ClassADelegate
- (void)run;
@property (nonatomic,copy) NSString *runDistance;
@end
@protocol ClassBDelegate
- (void)walk;
@property (nonatomic,copy) NSString *walkDistance;
@end
#import "ClassC.h"
#import "ClassA.h"
#import "ClassB.h"

@interface ClassC()<ClassADelegate,ClassBDelegate>

@end

@implementation ClassC
- (void)sport{
    [self run];
    [self walk];
}

- (void)run{
    self.runDistance = @"10000";
    NSLog(@"今天跑了%@步",self.runDistance);
}
- (void)walk{
    self.walkDistance = @"5000";
    NSLog(@"今天走了%@步",self.walkDistance);

}

3. 消息转发

当向某个对象发送消息,但runtime system在当前类以及父类中都找不到对应方法的实现时,runtime system并不会立即报错使程序崩溃,而是依次执行下列步骤:

image-20200627144159072

  1. 动态方法解析:向当前类发送resolveInstanceMethod: 信号,检查是否动态向该类添加了方法
  2. 快速消息转发:检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法,若该方法返回nil或者非self,则向该返回对象重新发送消息
  3. 标准消息转发:runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。

例子:

/* ---------------------- 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

3.1 快速转发

对应- (id)forwardingTargetForSelector:(SEL)aSelector;方法,他是Apple提供了一种更为简便的方式进行消息转发。作用:对消息进行重定向。当一个对象无法处理消息时,系统会调用这个方法,可以利用这个方法将消息的接受者替换为其他对象。

/* ---------------------- 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调用Cook方法时,无法处理,因此通过forwardingTargetForSelector重定向给mother。

3.2 标准转发

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

--------------------- 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

3.3 对比

快速转发和标准转发的区别:

  • 快速消息转发:简单、快速、但仅能转发给一个对象。
  • 标准消息转发:稍复杂、较慢、但转发操作实现可控,可以实现多对象转发。

为什么是快速转发是一对一,标准转发是一对多:

  • 快速转发是return,只能返回一个对象。
  • 标准转发是调用[anInvocation invokeWithTarget:XXX];能够反复调用多次。