iOS多继承的实现及区别

来自我的个人博客Minecode.link

多继承可以允许子类从多个父类派生,而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为私有方法(毕竟唱歌画画是要展示给大家的 :P

通过类别实现

下面就有请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关联对象,而消息转发的属性实现也是类似方法。
  • 分类和消息转发更接近于真正意义的多继承,也方便管理,添加删除父类方便。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容