8

iOS多继承的实现及区别

 3 years ago
source link: https://www.jianshu.com/p/c8124b866693
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
0.4172019.03.21 11:19:55字数 1,323阅读 145

多继承可以允许子类从多个父类派生,而Objective-C并不支持多继承,但我们仍可间接实现。

Objective-C实现多继承主要可以通过协议、分类、消息转发来实现。我们来总结一下其使用以及优缺点。

通过协议实现

协议主要是用来提出类应遵守的标准,但其特性也可用来实现多继承。一个类可以遵守多个协议,也即实现多个协议的方法,以此来达到多继承的效果。

概念上的单继承和多继承应该是继承父类的属性和方法,并且不经重写即可使用,但通过协议实现多继承有如下不同:

  • 子类需要实现协议方法
  • 由于协议无法定义属性,所以该方法只能实现方法的多继承

下面来看一下示例代码:

// 编程技能
@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

通过下面的协议遵守,该程序员类掌握了编程、唱歌、绘画多个技能。同时也可以根据遵守协议的位置绝对协议方法是否公开,上述代码中draw和sing方法公开,而program为私有方法(毕竟唱歌画画是要展示给大家的 )

通过类别实现

下面就有请Objective-C的一大黑魔法——Catagory(分类)。

相对于协议,它的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

通过添加分类,我们可以为程序员添加各种方法,同时通过Runtime这一黑魔法实现了动态添加属性,我们可以直接通过点语法为程序员设置座右铭。

同时,分类的文件在管理上也比较方便,如果不想用直接删除#import即可,灵活性较强。

关于Catagory添加属性的方式可以自行学习Runtime。

通过消息转发

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

  1. 动态方法解析: 通过resolveInstanceMethod:方法,检查是否通过@dynamic动态添加了方法。
  2. 直接消息转发: 不修改原本方法签名,直接检查forwardingTargetForSelector:是否实现,若返回非nil且非self,则向该返回对象直接转发消息。
  3. 标准消息转发: 先处理方法调用再转发消息,重写methodSignatureForSelector:forwardInvocation:方法,前者用于为该消息创建一个合适的方法签名,后者则是将该消息转发给其他对象。
  4. 上述过程均未实现,则程序异常。

我们现在尝试2和3两种方案:

直接消息转发

/*  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的实现同上述代码  */

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

包含父类方法

这个其实就是为Programmer添加Singer和Artist成员变量,并为前者提供接口调用后两者的方法。这种方式虽然在效果上实现了多继承,但是没有实现的意义,既然所有东西都要在子类实现一遍,那么也有悖于”继承”这一概念,在此不再赘述。

几种多继承方式的对比

方法 添加属性 添加方法 继承父类的实现 协议(Protocol) ○ ● ○ 分类(Catagory) ● ● ○ 消息转发 ● ● ●

对于上述的内容进行一些补充:

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

如需更好的阅读体验,请查阅原文:iOS多继承的实现及区别


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK