背景
我們都知道OC是不支持多繼承的,這是因?yàn)橄C(jī)制名稱查找發(fā)生在運(yùn)行時(shí)而非編譯時(shí),很難解決多個(gè)基類可能導(dǎo)致的二義性問題,那么如果我們想要一個(gè)類a同時(shí)繼承類b和類c,我們要如何做才能達(dá)到我們想要的結(jié)果呢?我們創(chuàng)建三個(gè)類ClassA、ClassB、ClassC,A和B分別有兩個(gè)方法run和walk,假設(shè)我們需要讓C同時(shí)擁有run和walk方法,我們可以通過下面幾種方法來達(dá)到我們想要的效果。
- 組合
在ClassC的.h或者.m文件中,聲明ClassA和ClassB的實(shí)例,這樣我們就可以通過調(diào)用A類和B類實(shí)例來執(zhí)行run和walk方法
@class ClassA,ClassB;
@interface ClassC : NSObject
@property (nonatomic,strong) ClassA *a;
@property (nonatomic,strong) ClassB *b;
- (void)sport;
@end
#import "ClassC.h"
#import "ClassA.h"
#import "ClassB.h"
@implementation ClassC
- (void)sport{
[self.a run];
[self.b walk];
}
@end
- delegate和protocol
將C類需要繼承的方法以及屬性在ClassA和ClassB中各自聲明一份協(xié)議,C類遵守這兩份協(xié)議,同時(shí)在C類中實(shí)現(xiàn)協(xié)議中的方法以及屬性
@protocol ClassADelegate
- (void)run;
@property (nonatomic,copy) NSString *runDistance;
@end
@protocol ClassBDelegate
- (void)walk;
@property (nonatomic,copy) NSString *walkDistance;
@end
#import "ClassC.h"
#import "ClassA.h"
#import "ClassB.h"
@interface ClassC()<ClassADelegate,ClassBDelegate>
@end
@implementation ClassC
- (void)sport{
[self run];
[self walk];
}
- (void)run{
self.runDistance = @"10000";
NSLog(@"今天跑了%@步",self.runDistance);
}
- (void)walk{
self.walkDistance = @"5000";
NSLog(@"今天走了%@步",self.walkDistance);
}
@synthesize walkDistance;
@synthesize runDistance;
@end
- (void)viewDidLoad {
[super viewDidLoad];
ClassC *c = [[ClassC alloc] init];
[c sport];
// Do any additional setup after loading the view, typically from a nib.
}
- 消息轉(zhuǎn)發(fā)(快速轉(zhuǎn)發(fā)和標(biāo)準(zhǔn)轉(zhuǎn)發(fā))
當(dāng)向某個(gè)對象發(fā)送消息,但runtime system在當(dāng)前類以及父類中都找不到對應(yīng)方法的實(shí)現(xiàn)時(shí),runtime system并不會立即報(bào)錯(cuò)使程序崩潰,而是依次執(zhí)行下列步驟
流程如下
- 動(dòng)態(tài)方法解析:向當(dāng)前類發(fā)送resolveInstanceMethod: 信號,檢查是否動(dòng)態(tài)向該類添加了方法
- 快速消息轉(zhuǎn)發(fā):檢查該類是否實(shí)現(xiàn)了 forwardingTargetForSelector: 方法,若實(shí)現(xiàn)了則調(diào)用這個(gè)方法,若該方法返回nil或者非self,則向該返回對象重新發(fā)送消息
- 標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā):runtime發(fā)送methodSignatureForSelector:消息獲取Selector對應(yīng)的方法簽名。返回值非空則通過forwardInvocation:轉(zhuǎn)發(fā)消息,返回值為空則向當(dāng)前對象發(fā)送doesNotRecognizeSelector:消息,程序崩潰退出.
消息轉(zhuǎn)發(fā)越到后面代價(jià)越大
實(shí)現(xiàn)過程
- 快速消息轉(zhuǎn)發(fā)的實(shí)現(xiàn)方法很簡單,只需要重寫- (id)forwardingTargetForSelector:(SEL)aSelector方法就可以了,我們來用例子證明,還是拿上面的ClassA和ClassB,假設(shè)B有walk方法,而A沒有,此時(shí)我需要也要有walk方法,這里我們有兩種方式來實(shí)現(xiàn)
1.強(qiáng)轉(zhuǎn)類型
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [[ClassA alloc] init];
[(ClassB *)a walk];
//[a run];
}
#import "ClassA.h"
#import "ClassB.h"
#import "ClassA+MyCategory.h"
@implementation ClassA
- (void)run{
NSLog(@"跑步");
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
// 當(dāng)然這里可以通過判斷方法名來決定轉(zhuǎn)發(fā)的對象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"])
ClassB *b = [[ClassB alloc] init];
if([b respondsToSelector:aSelector]){
return b;
}
return nil;
}
@end
- 增加Category
#import "ClassA.h"
@interface ClassA (MyCategory)
- (void)walk;
@end
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [[ClassA alloc] init];
[a walk];
}
通過運(yùn)行發(fā)現(xiàn)都會執(zhí)行- (id)forwardingTargetForSelector:(SEL)aSelector方法,然后再重新發(fā)送消息執(zhí)行ClassB的walk方法
- 標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)需要重寫 methodSignatureForSelector: 和 forwardInvocation: 兩個(gè)方法
#import "ClassA.h"
#import "ClassB.h"
#import "ClassA+MyCategory.h"
@interface ClassA(){
ClassB *_b;
}
@end
@implementation ClassA
- (id)init
{
self = [super init];
if (self) {
_b = [[ClassB alloc] init];
}
return self;
}
- (void)run{
NSLog(@"跑步");
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
// 當(dāng)然這里可以通過判斷方法名來決定轉(zhuǎn)發(fā)的對象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"])
//ClassB *b = [[ClassB alloc] init];
if(signature == nil){
signature = [_b methodSignatureForSelector:aSelector];
}
NSUInteger argCount = [signature numberOfArguments];
for (NSInteger i=0; i<argCount; i++) {
NSLog(@"%s" , [signature getArgumentTypeAtIndex:i]);
}
NSLog(@"returnType:%s ,returnLen:%ld" , [signature methodReturnType] , [signature methodReturnLength]);
NSLog(@"signature:%@" , signature);
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// 當(dāng)然這里可以通過判斷方法名來決定轉(zhuǎn)發(fā)的對象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"])
SEL selector = [anInvocation selector];
//ClassB *b = [[ClassB alloc] init];
if([_b respondsToSelector:selector]){
//這里如果沒有響應(yīng),系統(tǒng)則會報(bào)錯(cuò)崩潰
[anInvocation invokeWithTarget:_b];
}
}
@end
運(yùn)行結(jié)果
2018-01-04 10:32:34.288075+0800 MultipleInheritanceDemo[2006:407016] @
2018-01-04 10:32:34.288303+0800 MultipleInheritanceDemo[2006:407016] :
2018-01-04 10:32:34.288399+0800 MultipleInheritanceDemo[2006:407016] returnType:v ,returnLen:0
2018-01-04 10:32:34.288592+0800 MultipleInheritanceDemo[2006:407016] signature:<NSMethodSignature: 0x60000047a540>
2018-01-04 10:32:34.288887+0800 MultipleInheritanceDemo[2006:407016] 散步
- 類別(category)
類別也可以用來模擬多繼承,比如給當(dāng)前類添加方法,利用runTime來添加屬性,方法不表,在前一篇文章Category與Extension有實(shí)現(xiàn)
- NSProxy
我們都知道NSObject是Objective-C中大部分類的基類。但不是很多人知道除了NSObject之外的另一個(gè)基類——NSProxy,蘋果官方文檔是這樣描述的
NSProxy is an abstract superclass defining an API for objects that act as
stand-ins for other objects or for objects that don’t exist yet. Typically, a
message to a proxy is forwarded to the real object or causes the proxy to
load (or transform itself into) the real object. Subclasses of NSProxy can be
used to implement transparent distributed messaging (for example,NSDistantObject) or for lazy instantiation of objects that are expensive to
create...
總的來說,NSProxy是一個(gè)虛類,你可以通過繼承它,并重寫這兩個(gè)方法以實(shí)現(xiàn)消息轉(zhuǎn)發(fā)到另一個(gè)實(shí)例
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
這里推薦一篇文章,寫的非常詳細(xì)明白點(diǎn)我點(diǎn)我
總結(jié)
好了以上就上關(guān)于如何實(shí)現(xiàn)iOS的“多繼承”,大家還有其他的補(bǔ)充和建議可以留言。
參考資料
念茜的博客