多继承

460 阅读4分钟

1、组合方式

用 ClassC 添加 ClassA 、ClassB 成员变量 来调用 methodA、methodB;

@interface ClassA : NSObject {

}
- (void)methodA;
@end

//定义ClassB以及其methodB
@interface ClassB : NSObject {

}
- (void)methodB;
@end

//定义ClassC以及其需要的methodA,methodB
@interface ClassC : NSObject {
  ClassA *a;
  ClassB *b;
}

-(id)initWithA:(ClassA *)A b:(ClassB *)B;
-(void)methodA;
-(void)methodB;
@end

//注意在ClassC的实现
@implementation  ClassC
-(id)initWithA:(ClassA *)A b:(ClassB *)B{
     a=[[ClassA alloc] initWithClassA: A];//[A copy];
     b=[[ClassB alloc] initWithClassB: B];//[B copy];
}
-(void)methodA{
		[a methodA];
}
-(void)methodB{
		[b methodB];
}

2、协议protocol

设置 ClassA delegate 和 ClasssB delegate 以及实现方法 ClassA 里的 methodA ,和 ClasssB 里的methodB。ClassC遵守这两个协议就可以。

// 编程技能
@protocol Program <NSObject>
- (void)program;
@end

// 绘画技能
@protocol Draw <NSObject>
- (void)draw;
@end

// 歌唱技能
@protocol Sing <NSObject>
- (void)sing;
@end

// 原本一个什么也不会的程序员
// 学会了多个技能
@interface Programmer : NSObject <Draw, Sing>
// 继承的协议方法自动公有,无须声明接口
@end

@interface Programmer () <Program>
// 继承的协议方法自动私有,无须声明接口
@end

// 需要自行实现协议方法
@implementation Programmer
- (void)program {
    NSLog(@"I'm writing bugs!");
}
- (void)draw {
    NSLog(@"I can draw");
}
- (void)sing {
    NSLog(@"Lalalallalalala");
}
@end

3、分类

相对于协议,它的Runtime特性造就了其一定优势。

  • 可以为类添加方法;
  • 可以为类添加实例(通过Runtime),这是协议做不到的;
  • 分类方便管理。
#import "Programmer.h"
@interface Programmer (Program)
// 声明属性
@property (nonatomic, assign) NSString *motto;
// 声明公有方法
- (void)draw;
- (void)sing;
@end

#import <objc/runtime.h>
@implementation Programmer (Program)
// 为Catagory添加属性
static const char kMottoKey;
- (void)setMotto:(NSString *)motto {
    objc_setAssociatedObject(self, &kMottoKey, motto, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)motto {
    return objc_getAssociatedObject(self, &kMottoKey);
}
// 私有方法
- (void)program {
    NSLog(@"I'm writing bugs!");
}
// 实现公有方法
- (void)draw {
    NSLog(@"I can draw");
}
- (void)sing {
    NSLog(@"Lalalalallalala");
}
@end

4、消息转发 消息转发也是Runtime的黑魔法,其中一个用处就是可以实现多继承,当发送消息后找不到对应的方法实现时,会经过如下过程。

(1)动态方法解析: 通过resolveInstanceMethod:方法,检查是否通过@dynamic动态添加了方法。

(2)直接消息转发: 不修改原本方法签名,直接检查forwardingTargetForSelector:是否实现,若返回非nil且非self,则向该返回对象直接转发消息。

(3)标准消息转发: 先处理方法调用再转发消息,重写methodSignatureForSelector:forwardInvocation:方法,前者用于为该消息创建一个合适的方法签名,后者则是将该消息转发给其他对象。

直接消息转发:

/*  Programmer实现文件  */
@implementation Programmer

// 通过消息转发实现多继承
- (id)forwardingTargetForSelector:(SEL)aSelector {
    Singer *singer = [[Singer alloc] init];
    Artist *artist = [[Artist alloc] init];
    
    if ([singer respondsToSelector:aSelector]) {
        return singer;
    }
    else if ([artist respondsToSelector:aSelector]) {
        return artist;
    }
    return nil;
}
@end

/*  Artist代码  */
@interface Artist : NSObject
// 画家可以绘画
- (void)draw;
@end

@implementation Artist
- (void)draw {
    NSLog(@"I can draw");
}
@end

/*  Singer代码  */
@interface Singer : NSObject
// 歌手会唱歌
- (void)sing;
@end

@implementation Singer
- (void)sing {
    NSLog(@"Lalalalalala");
}
@end

通过直接转发消息到其他类型的对象,我们就实现了多继承。如下调用,结果能够完成唱歌和绘画

Programmer *programmer = [[Programmer alloc] init];

// 在performSelector中使用NSSelectorFromString会造成警告
// 可以通过设置不检测performSelector内存泄露关闭警告
[programmer performSelector:NSSelectorFromString(@"sing") withObject:nil];

// 或者通过类型强转来实现,无警告
[(Artist *)programmer draw];

通过消息转发,我们实现了动态性,及真正的将方法交给其他类来实现,而非协议或者分类所需要自行实现。同时,消息转发也给我们了充分的灵活性,如上代码,我们可以在Programer类声明sing和draw方法,但也可不暴露这些接口而通过类型强转来调用这两个方法。

标准消息转发:

标准消息转发相对于直接消息转发更加高级,可以由程序员控制转发的过程,同时也可以实现对多个对象的转发,直接消息转发仅能把该方法直接转发给其他某对象。

@interface Programmer () {
    // 由于要频繁用到,我们可以创建成员实例
    Singer *_singer;
    Artist *_artist;
}
@end

@implementation Programmer

- (instancetype)init {
    if (self = [super init]) {
        _singer = [[Singer alloc] init];
        _artist = [[Artist alloc] init];
    }
    return self;
}

// 在转发消息前先对方法重新签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    // 尝试自行实现方法签名
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    
    // 若无法实现,尝试通过多继承得到的方法实现
    if (signature == nil) {
        // 判断该方法是哪个父类的,并通过其创建方法签名
        if ([_singer respondsToSelector:aSelector]) {
            signature = [_singer methodSignatureForSelector:aSelector];
        }
        else if ([_artist respondsToSelector:aSelector]) {
            signature = [_artist methodSignatureForSelector:aSelector];
        }
    }
    
    return signature;
}

// 为方法签名后,转发消息
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    
    // 判断哪个类实现了该方法
    if ([_singer respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:_singer];
    }
    else if ([_artist respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:_artist];
    }
}

@end

/*  Singer和Artist的实现同上述代码  */

调用后,正常执行。实际上在实现多继承方面并没有必要使用标准消息转发,它可以在我们需要对消息进行处理时发挥其优势。

方法添加属性添加方法继承父类的实现
协议(Protocol)
分类(Catagory)
消息转发
  • 分类的属性实现是通过Runtime关联对象,而消息转发的属性实现也是类似方法。
  • 分类和消息转发更接近于真正意义的多继承,也方便管理,添加删除父类方便。