簡介
多繼承可以允許子類從多個父類派生,而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)并注明出處。