iOS 中几种多继承的实现方式

单继承与多继承概念

继承是面向对象的基本特征之一,在具体语言的语法上设计有两种形式:多继承与单继承。

单继承

一个子类只有一个父类

  • 优点:类层次机结构清晰,设计上更容易把握
  • 缺点:在丰富度要求较高和较复杂的情况下,单继承从设计结构上无法满足

多继承

一个子类可以有多个父类

  • 优点:由于同时具备多个父类的特征,让子类拥有更高的丰富度
  • 缺点:会让继承的结构变得更复杂,而且会出现菱形继承的风险

Objective-C 不支持多继承,但我们可以间接的实现(协议、分类、消息转发)

通过协议实现多继承

一个类可以遵守多个协议,需要实现多个协议的方法,以此来达到多继承的效果。概念上的多继承和多继承应该是继承父类的属性和方法,并且不需要重写即可使用,通过协议实现多继承有以下不同:

  • 子类需要实现协议方法
  • 由于协议无法定义属性(只是声明 setter/getter 方法),所以该方法只能实现方法的多继承

通过一个示例来理解:创建一个 Person 类,继承自 NSObject,添加三个协议,如下:

/**------.h------*/
//游泳
@protocol Swim <NSObject>

- (void)swim;

@end

//吃饭
@protocol Eat <NSObject>

- (void)eat;

@end

//睡觉
@protocol Sleep <NSObject>

- (void)sleep;

@end

//添加游泳、吃饭技能,继承的协议方法自动公有
@interface Person : NSObject<Swim, Eat>

@end

/**------.m------*/
//添加睡觉技能,继承的协议方法自动私有
@interface Person () <Sleep>

@end

@implementation Person

//需要实现协议方法
- (void)swim {
    NSLog(@"游泳");
}

- (void)eat {
    NSLog(@"吃饭");
}

- (void)sleep {
    NSLog(@"睡觉");
}

@end

原本的 Person 类什么都没有,通过遵守协议,使 Person 类拥有了 游泳吃饭睡觉 三个功能。协议的位置决定协议方法是否公开,上述 Personswimeat 方法是公开的,sleep 方法是私有的。

通过类别实现多继承

相对于协议,分类方法有一定的优势

  • 可以为分类添加方法
  • 可以为分类添加实例(通过 runtime 的关联属性),这是协议做不到的
  • 分类方便管理

同样的我们用分类实现上述协议实现的功能外再添加一个属性,创建一个 Person 的分类,如下:

/**------.h------*/
@interface Person (Ability)

// 声明属性
@property (nonatomic, copy) NSString *username;

// 声明共有方法
- (void)swim;
- (void)eat;

@end

/**------.m------*/
@implementation Person (Ability)

// 为分类添加属性
static const char *kUSerName = "kUserName";

- (NSString *)username {
    return objc_getAssociatedObject(self, kUSerName);
}

- (void)setUsername:(NSString *)username {
    objc_setAssociatedObject(self, kUSerName, username, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

// 实现公有方法
- (void)swim {
    NSLog(@"游泳");
}

- (void)eat {
    NSLog(@"吃饭");
}

// 私有方法
- (void)sleep {
    NSLog(@"睡觉");
}

@end

通过添加分类,我们可以添加各种方法,同时 Runtime 为我们实现了动态添加属性,可以直接通过点语法设置属性。而且分类文件在管理上也比较方便,灵活性更强。

通过消息转发实现多继承

关于消息转发的流程可以查看 objc_msgSend 流程分析(消息转发),这里我只针对 快速转发慢速转发 这两步来实现上述功能

快速转发实现

我们通过重写 forwardingTargetForSelector 方法来转发对象

// Swim 类--游泳
@interface Swim : NSObject

- (void)swim;

@end

@implementation Swim

- (void)swim {
    NSLog(@"游泳");
}

@end

// Eat 类--吃饭
@interface Eat : NSObject

- (void)eat;

@end

@implementation Eat

- (void)eat {
    NSLog(@"吃饭");
}

@end

@implementation Person

// 快速消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
    Swim *swim = [[Swim alloc] init];
    Eat  *eat  = [[Eat alloc] init];
    
    if ([swim respondsToSelector:aSelector]) {
        return swim; // 转发给 swim 对象
    }
    else if ([eat respondsToSelector:aSelector]) {
        return eat; // 转发给 eat 对象
    }
    return nil;
}

@end

通过消息转发给其他对象实现了多继承,调用如下

Person *p = [[Person alloc] init];

//1.在performSelector中使用NSSelectorFromString会造成警告,可以通过设置不检测performSelector内存泄露关闭警告
[p performSelector:NSSelectorFromString(@"eat")];

//2.类型强转
[(Eat *)p eat];

通过消息的快速转发,实现了动态性,真正的将方法交给其他类来实现,而非协议或者分类所需要自行实现。同时,消息转发也给我们了充分的灵活性,在不暴露这些接口的情况下通过类型强转来调用。

慢速转发实现

慢速转发由程序员控制转发的过程,同时也可以实现对多个对象的转发,快速转发只能把该方法直接转发给其他某对象

@interface Person ()
{
    // 创建成员实例因为后面会频繁用到
    Swim *_swim;
    Eat  *_eat;
}

@end

@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        _swim = [[Swim alloc] init];
        _eat  = [[Eat alloc] init];
    }
    
    return self;
}

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    // 尝试自行实现方法签名
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    if (methodSignature == nil) { // 若无法实现,尝试通过多继承得到的方法实现
        
        // 判断方法是哪个父类的,通过其创建方法签名
        if ([_swim respondsToSelector:aSelector]) {
            methodSignature = [_swim methodSignatureForSelector:aSelector];
        }
        else if ([_eat respondsToSelector:aSelector]) {
            methodSignature = [_eat methodSignatureForSelector:aSelector];
        }
    }
    return methodSignature;
}

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

@end

通过上述慢速消息转发在效果上实现了多继承,但是没有实现的意义,因为所有的东西都要在子类实现一遍。

分类和消息转发更接近于真正意义的多继承,也方便管理,添加删除父类方便

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

推荐阅读更多精彩内容