iOS多繼承的實現(xiàn)及區(qū)別

簡介

多繼承可以允許子類從多個父類派生,而Objective-C并不支持多繼承,但我們?nèi)钥砷g接實現(xiàn)。

Objective-C實現(xiàn)多繼承主要可以通過協(xié)議、分類、消息轉(zhuǎn)發(fā)來實現(xiàn)。我們來總結(jié)一下其使用以及優(yōu)缺點。

通過協(xié)議實現(xiàn)

協(xié)議主要是用來提出類應(yīng)遵守的標(biāo)準(zhǔn),但其特性也可用來實現(xiàn)多繼承。一個類可以遵守多個協(xié)議,也即實現(xiàn)多個協(xié)議的方法,以此來達(dá)到多繼承的效果。

概念上的單繼承和多繼承應(yīng)該是繼承父類的屬性和方法,并且不經(jīng)重寫即可使用,但通過協(xié)議實現(xiàn)多繼承有如下不同:

a.子類需要實現(xiàn)協(xié)議方法
b.由于協(xié)議無法定義屬性,所以該方法只能實現(xiàn)方法的多繼承

下面來看一下示例代碼:

// 編程技能
@protocol Program <NSObject>
- (void)program;
@end
// 繪畫技能
@protocol Draw <NSObject>
- (void)draw;
@end
// 歌唱技能
@protocol Sing <NSObject>
- (void)sing;
@end

// 原本一個什么也不會的程序員
// 學(xué)會了多個技能
@interface Programmer : NSObject <Draw, Sing>
// 繼承的協(xié)議方法自動公有,無須聲明接口
@end

@interface Programmer () <Program>
// 繼承的協(xié)議方法自動私有,無須聲明接口
@end

// 需要自行實現(xiàn)協(xié)議方法
@implementation Programmer
- (void)program {
    NSLog(@"I'm writing bugs!");
}
- (void)draw {
    NSLog(@"I can draw");
}
- (void)sing {
    NSLog(@"Lalalallalalala");
}
@end

通過下面的協(xié)議遵守,該程序員類掌握了編程、唱歌、繪畫多個技能。同時也可以根據(jù)遵守協(xié)議的位置絕對協(xié)議方法是否公開,上述代碼中draw和sing方法公開,而program為私有方法(畢竟唱歌畫畫是要展示給大家的 :P

通過類別實現(xiàn)

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

相對于協(xié)議,它的Runtime特性造就了其一定優(yōu)勢:

a.可以為類添加方法
b.可以為類添加實例(通過Runtime),這是協(xié)議做不到的
c.分類方便管理

下面來看一下通過分類來實現(xiàn)多繼承:

/*      分類頭文件       */
#import "Programmer.h"

@interface Programmer (Program)
// 聲明屬性
@property (nonatomic, assign) NSString *motto;
// 聲明公有方法
- (void)draw;
- (void)sing;
@end


/*      分類實現(xiàn)文件       */
#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!");
}
// 實現(xiàn)公有方法
- (void)draw {
    NSLog(@"I can draw");
}
- (void)sing {
    NSLog(@"Lalalalallalala");
}
@end

通過添加分類,我們可以為程序員添加各種方法,同時通過Runtime這一黑魔法實現(xiàn)了動態(tài)添加屬性,我們可以直接通過點語法為程序員設(shè)置座右銘。

同時,分類的文件在管理上也比較方便,如果不想用直接刪除#import即可,靈活性較強。

關(guān)于Catagory添加屬性的方式可以自行學(xué)習(xí)Runtime。

通過消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)也是Runtime的黑魔法,其中一個用處就是可以實現(xiàn)多繼承,當(dāng)發(fā)送消息后找不到對應(yīng)的方法實現(xiàn)時,會經(jīng)過如下過程:

1.動態(tài)方法解析: 通過resolveInstanceMethod:方法,檢查是否通過@dynamic動態(tài)添加了方法。
2.直接消息轉(zhuǎn)發(fā): 不修改原本方法簽名,直接檢查forwardingTargetForSelector:是否實現(xiàn),若返回非nil且非self,則向該返回對象直接轉(zhuǎn)發(fā)消息。
3.標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā): 先處理方法調(diào)用再轉(zhuǎn)發(fā)消息,重寫methodSignatureForSelector:和forwardInvocation:方法,前者用于為該消息創(chuàng)建一個合適的方法簽名,后者則是將該消息轉(zhuǎn)發(fā)給其他對象。
4.上述過程均未實現(xiàn),則程序異常。

我們現(xiàn)在嘗試2和3兩種方案:

/*  Programmer實現(xiàn)文件  */
@implementation Programmer

// 通過消息轉(zhuǎn)發(fā)實現(xiàn)多繼承
- (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

通過直接轉(zhuǎn)發(fā)消息到其他類型的對象,我們就實現(xiàn)了多繼承。如下調(diào)用,結(jié)果能夠完成唱歌和繪畫

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

// 在performSelector中使用NSSelectorFromString會造成警告
// 可以通過設(shè)置不檢測performSelector內(nèi)存泄露關(guān)閉警告
[programmer performSelector:NSSelectorFromString(@"sing") withObject:nil];

// 或者通過類型強轉(zhuǎn)來實現(xiàn),無警告
[(Artist *)programmer draw];

通過消息轉(zhuǎn)發(fā),我們實現(xiàn)了動態(tài)性,及真正的將方法交給其他類來實現(xiàn),而非協(xié)議或者分類所需要自行實現(xiàn)。同時,消息轉(zhuǎn)發(fā)也給我們了充分的靈活性,如上代碼,我們可以在Programer類聲明sing和draw方法,但也可不暴露這些接口而通過類型強轉(zhuǎn)來調(diào)用這兩個方法。

標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)

標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)相對于直接消息轉(zhuǎn)發(fā)更加高級,可以由程序員控制轉(zhuǎn)發(fā)的過程,同時也可以實現(xiàn)對多個對象的轉(zhuǎn)發(fā),直接消息轉(zhuǎn)發(fā)僅能把該方法直接轉(zhuǎn)發(fā)給其他某對象。

標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)代碼如下

@interface Programmer ()
{
    // 由于要頻繁用到,我們可以創(chuàng)建成員實例
    Singer *_singer;
    Artist *_artist;
}
@end

@implementation Programmer

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

// 在轉(zhuǎn)發(fā)消息前先對方法重新簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    // 嘗試自行實現(xiàn)方法簽名
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    
    // 若無法實現(xiàn),嘗試通過多繼承得到的方法實現(xiàn)
    if (signature == nil) {
        // 判斷該方法是哪個父類的,并通過其創(chuàng)建方法簽名
        if ([_singer respondsToSelector:aSelector]) {
            signature = [_singer methodSignatureForSelector:aSelector];
        }
        else if ([_artist respondsToSelector:aSelector]) {
            signature = [_artist methodSignatureForSelector:aSelector];
        }
    }
    
    return signature;
}

// 為方法簽名后,轉(zhuǎn)發(fā)消息
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    
    // 判斷哪個類實現(xiàn)了該方法
    if ([_singer respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:_singer];
    }
    else if ([_artist respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:_artist];
    }
}

@end

/*  Singer和Artist的實現(xiàn)同上述代碼  */

調(diào)用后,正常執(zhí)行。實際上在實現(xiàn)多繼承方面并沒有必要使用標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā),它可以在我們需要對消息進(jìn)行處理時發(fā)揮其優(yōu)勢。

包含父類方法

這個其實就是為Programmer添加Singer和Artist成員變量,并為前者提供接口調(diào)用后兩者的方法。這種方式雖然在效果上實現(xiàn)了多繼承,但是沒有實現(xiàn)的意義,既然所有東西都要在子類實現(xiàn)一遍,那么也有悖于"繼承"這一概念,在此不再贅述。

幾種多繼承方式的對比

方法 添加屬性 添加方法 繼承父類的實現(xiàn)
協(xié)議(Protocol)
分類(Catagory)
消息轉(zhuǎn)發(fā)

對于上述的內(nèi)容進(jìn)行一些補充:

a.分類的屬性實現(xiàn)是通過Runtime關(guān)聯(lián)對象,而消息轉(zhuǎn)發(fā)的屬性實現(xiàn)也是類似方法。
b.分類和消息轉(zhuǎn)發(fā)更接近于真正意義的多繼承,也方便管理,添加刪除父類方便。

作者:Minecode
鏈接:http://m.itdecent.cn/p/9601e84177a3
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。

FAQ Runtime待復(fù)習(xí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容