問(wèn)題:OC有多繼承嗎掂器?沒(méi)有的話用什么代替亚皂?

背景

有三個(gè)類ClassA、ClassB国瓮、ClassC灭必,A和B分別有兩個(gè)方法run和walk,假設(shè)我們需要讓C同時(shí)擁有run和walk方法狞谱,我們要如何做才能達(dá)到我們想要的結(jié)果呢?

我們都知道OC是不支持多繼承的禁漓,這是因?yàn)橄C(jī)制名稱查找發(fā)生在運(yùn)行時(shí)而非編譯時(shí)跟衅,很難解決多個(gè)基類可能導(dǎo)致的二義性問(wèn)題,但我們可以通過(guò)下面幾種方法來(lái)達(dá)到我們想要的效果播歼。

  • delegate(代理)
  • 組合
  • 消息轉(zhuǎn)發(fā)(快速轉(zhuǎn)發(fā)和標(biāo)準(zhǔn)轉(zhuǎn)發(fā))
  • 類別(category)
  • NSProxy

一伶跷、delegate(代理)

將C類需要繼承的方法以及屬性在ClassA和ClassB中各自聲明一份協(xié)議,C類遵守這兩份協(xié)議秘狞,同時(shí)在C類中實(shí)現(xiàn)協(xié)議中的方法以及屬性

#ClassA的聲明協(xié)議
@protocol ClassADelegate
- (void)run;
@property (nonatomic,copy) NSString *runDistance;
@end
#ClassB的聲明協(xié)議
@protocol ClassBDelegate
- (void)walk;
@property (nonatomic,copy) NSString *walkDistance;
@end
#ClassC遵守ClassADelegate撩穿、ClassBDelegate兩份協(xié)議
#同時(shí)在ClassC中實(shí)現(xiàn)ClassADelegate、ClassBDelegate兩份協(xié)議中的方法以及屬性
#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

二谒撼、組合

在ClassC的.h或者.m文件中,聲明ClassA和ClassB的實(shí)例,這樣我們就可以通過(guò)調(diào)用A類和B類實(shí)例來(lái)執(zhí)行run和walk方法

#ClassC.h文件 聲明ClassA和ClassB的實(shí)例
@class ClassA,ClassB;
@interface ClassC : NSObject
@property (nonatomic,strong) ClassA *a;
@property (nonatomic,strong) ClassB *b;
- (void)sport;
@end
#ClassC.m文件 調(diào)用A類和B類實(shí)例來(lái)執(zhí)行run和walk方法
#import "ClassC.h"
#import "ClassA.h"
#import "ClassB.h"

@implementation ClassC
- (void)sport{
    [self.a run];
    [self.b walk];
}
@end

三雾狈、消息轉(zhuǎn)發(fā)(快速轉(zhuǎn)發(fā)和標(biāo)準(zhǔn)轉(zhuǎn)發(fā))

當(dāng)向某個(gè)對(duì)象發(fā)送消息廓潜,但runtime system在當(dāng)前類以及父類中都找不到對(duì)應(yīng)方法的實(shí)現(xiàn)時(shí),runtime system并不會(huì)立即報(bào)錯(cuò)使程序崩潰善榛,而是依次執(zhí)行下列步驟:


轉(zhuǎn)發(fā)機(jī)制
  • 1.動(dòng)態(tài)方法解析:向當(dāng)前類發(fā)送resolveInstanceMethod: 信號(hào)辩蛋,檢查是否動(dòng)態(tài)向該類添加了方法
  • 2.快速消息轉(zhuǎn)發(fā):檢查該類是否實(shí)現(xiàn)了 forwardingTargetForSelector: 方法,若實(shí)現(xiàn)了則調(diào)用這個(gè)方法移盆,若該方法返回nil或者非self悼院,則向該返回對(duì)象重新發(fā)送消息。
    快速消息轉(zhuǎn)發(fā)的實(shí)現(xiàn)方法很簡(jiǎn)單咒循,只需要重寫(xiě)- (id)forwardingTargetForSelector:(SEL)aSelector方法就可以了据途,我們來(lái)用例子證明,還是拿上面的ClassA和ClassB叙甸,假設(shè)B有walk方法颖医,而A沒(méi)有。
#import "ClassA.h"
#import "ClassB.h"
#import "ClassA+MyCategory.h"

@implementation ClassA
- (void)run{
  NSLog(@"跑步");
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
  // 當(dāng)然這里可以通過(guò)判斷方法名來(lái)決定轉(zhuǎn)發(fā)的對(duì)象
  //NSString *seletorName = NSStringFromSelector(aSelector);
  //if([seletorName isEqualToString : @"xxx"])
  ClassB *b = [[ClassB alloc] init];
  if([b respondsToSelector:aSelector]){
      return b;
  }
  return nil;
}
@end
#調(diào)用
- (void)viewDidLoad {
   [super viewDidLoad];
   ClassA *a = [[ClassA alloc] init];
   [(ClassB *)a walk];
   //[a run];
}
  • 3.標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā):runtime發(fā)送methodSignatureForSelector:消息獲取Selector對(duì)應(yīng)的方法簽名裆蒸。返回值非空則通過(guò)forwardInvocation:轉(zhuǎn)發(fā)消息熔萧,返回值為空則向當(dāng)前對(duì)象發(fā)送doesNotRecognizeSelector:消息,程序崩潰退出僚祷。
    標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)需要重寫(xiě) 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)然這里可以通過(guò)判斷方法名來(lái)決定轉(zhuǎn)發(fā)的對(duì)象
   //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)然這里可以通過(guò)判斷方法名來(lái)決定轉(zhuǎn)發(fā)的對(duì)象
   //NSString *seletorName = NSStringFromSelector(aSelector);
   //if([seletorName isEqualToString : @"xxx"])
   SEL selector = [anInvocation selector];
   //ClassB *b = [[ClassB alloc] init];
   if([_b respondsToSelector:selector]){
       //這里如果沒(méi)有響應(yīng)佛致,系統(tǒng)則會(huì)報(bào)錯(cuò)崩潰
       [anInvocation invokeWithTarget:_b];
   }
}
@end
運(yùn)行結(jié)果
2020-01-04 10:32:34.288075+0800 MethodSwizzlingDemo[2006:407016] @
2020-01-04 10:32:34.288303+0800 MethodSwizzlingDemo[2006:407016] :
2020-01-04 10:32:34.288399+0800 MethodSwizzlingDemo[2006:407016] returnType:v ,returnLen:0
2020-01-04 10:32:34.288592+0800 MethodSwizzlingDemo[2006:407016] signature:<NSMethodSignature: 0x60000047a540>
2020-01-04 10:32:34.288887+0800 MethodSwizzlingDemo[2006:407016] 散步

四、類別(category)

類別也可以用來(lái)模擬多繼承辙谜,比如

  • 當(dāng)前類添加方法俺榆。
  • 利用runTime來(lái)添加屬性。
#import "ClassA.h"
@interface ClassA (MyCategory)
- (void)walk;//添加方法
@property (nonatomic,copy)NSString *myselfStr;//利用runTime來(lái)添加屬性
@end

@implementation ClassA (MyCategory)

static NSString *const myStr;

- (void)walk {
  NSLog(@"走路" );
}
- (void)setMyselfStr:(NSString *)myselfStr {
    objc_setAssociatedObject(self, &myStr, myselfStr, OBJC_ASSOCIATION_COPY);
}

- (NSString *)myselfStr {
    return objc_getAssociatedObject(self, &myStr);
}
@end
- (void)viewDidLoad {
   [super viewDidLoad];
   ClassA *a = [[ClassA alloc] init];
   [a walk];
}

五筷弦、NSProxy

我們都知道NSObject是Objective-C中大部分類的基類肋演。但不是很多人知道除了NSObject之外的另一個(gè)基類——NSProxy,蘋(píng)果官方文檔是這樣描述的

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è)抽象基類抑诸,它是為一些作為對(duì)象的替身或者并不存在的對(duì)象定義的API。一般的爹殊,發(fā)送給代理的消息被轉(zhuǎn)發(fā)給一個(gè)真實(shí)的對(duì)象或者代理本身引起加載(或者將本身轉(zhuǎn)換成)一個(gè)真實(shí)的對(duì)象蜕乡。NSProxy的基類可以被用來(lái)透明的轉(zhuǎn)發(fā)消息或者耗費(fèi)巨大的對(duì)象的lazy 初始化。

就是說(shuō)梗夸,NSProxy是一個(gè)虛類层玲,你可以通過(guò)繼承它,并重寫(xiě)這兩個(gè)方法以實(shí)現(xiàn)消息轉(zhuǎn)發(fā)到另一個(gè)實(shí)例

- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;

現(xiàn)在NSProxy的真面目終于浮出水面:

  • NSProxy 是NSProxy 是一個(gè)抽象類,跟 NSObject 一樣的基類反症,都遵守<NSObject>協(xié)議
  • NSProxy是一個(gè)抽象類辛块,必須繼承實(shí)例化其子類才能使用。
  • NSProxy從類名來(lái)看是代理類專門(mén)負(fù)責(zé)代理對(duì)象轉(zhuǎn)發(fā)消息的铅碍。相比NSObject類來(lái)說(shuō)NSProxy更輕量級(jí)润绵,通過(guò)NSProxy可以幫助Objective-C間接的實(shí)現(xiàn)多重繼承的功能。

還是以ClassA胞谈、ClassB尘盼、ClassC為例:

-------------------------------------------------
//  ClassA.h
#import <Foundation/Foundation.h>

#ClassA的聲明協(xié)議
@protocol ClassADelegate
- (void)run;
@end

@interface ClassA : NSObject
@end
-------------------------------------------------
//  ClassA.m
#import "ClassA.h"

@interface ClassA () < ClassADelegate >
@end

@implementation ClassA
- (void)run {
    NSLog(@"跑步");
}
@end
-------------------------------------------------
-------------------------------------------------
//  ClassB.h
#import <Foundation/Foundation.h>

#ClassB的聲明協(xié)議
@protocol ClassBDelegate
- (void)walk;
@end

@interface ClassB : NSObject
@end
-------------------------------------------------
//  ClassB.m
#import "ClassB.h"

@interface ClassB () < ClassBDelegate >
@end

@implementation ClassB
- (void)walk {
    NSLog(@"走路");
}
@end
-------------------------------------------------

一定要通過(guò)protocol來(lái)聲明接口,而不是直接在類的@interfere中定義烦绳。因?yàn)橥ㄟ^(guò)protocol來(lái)聲明接口卿捎,然后讓proxy類遵循此協(xié)議,可以騙過(guò)編譯器防止編譯器提示proxy類未聲明接口的錯(cuò)誤径密。這個(gè)問(wèn)題下面可以看到午阵。

-------------------------------------------------
//  ClassC.h
#ClassC這個(gè)子類必須要遵循之前定義的兩個(gè)協(xié)議ClassADelegate與ClassBDelegate
#目的是騙過(guò)編譯器,讓編譯器認(rèn)為這個(gè)類實(shí)現(xiàn)了上面兩個(gè)協(xié)議

#import <Foundation/Foundation.h>
#import "ClassA.h"
#import "ClassB.h"

@interface ClassC : NSProxy < ClassADelegate, ClassBDelegate >
+ (instancetype )dealerProxy;
@end
-------------------------------------------------
//  ClassC.m

#import "ClassC.h"
#import <objc/runtime.h>

@interface ClassC () {
    ClassA          *_classA;
    ClassC       *_classB;
    NSMutableDictionary     *_methodsMap;
}
@end

@implementation ClassC

#pragma mark - class method
+ (instancetype)dealerProxy{
    return [[ClassC alloc] init];
}

#pragma mark - init
- (instancetype)init{
    _methodsMap = [NSMutableDictionary dictionary];
    _ classA = [[ClassA alloc] init];
    _classB = [[ClassB alloc] init];

    //映射target及其對(duì)應(yīng)方法名
    [self _registerMethodsWithTarget: classA];
    [self _registerMethodsWithTarget: _classB];

    return self;
}

#pragma mark - private method
- (void)_registerMethodsWithTarget:(id )target{
    
    unsigned int numberOfMethods = 0;
    
    //獲取target方法列表
    Method *method_list = class_copyMethodList([target class], &numberOfMethods);
    
    for (int i = 0; i < numberOfMethods; i ++) {
        //獲取方法名并存入字典
        Method temp_method = method_list[i];
        SEL temp_sel = method_getName(temp_method);
        const char *temp_method_name = sel_getName(temp_sel);
        [_methodsMap setObject:target forKey:[NSString stringWithUTF8String:temp_method_name]];
    }
    
    free(method_list);
}
#pragma mark - NSProxy override methods
- (void)forwardInvocation:(NSInvocation *)invocation{
    //獲取當(dāng)前選擇子
    SEL sel = invocation.selector;
    
    //獲取選擇子方法名
    NSString *methodName = NSStringFromSelector(sel);
    
    //在字典中查找對(duì)應(yīng)的target
    id target = _methodsMap[methodName];
    
    //檢查target
    if (target && [target respondsToSelector:sel]) {
        [invocation invokeWithTarget:target];
    } else {
        [super forwardInvocation:invocation];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    //獲取選擇子方法名
    NSString *methodName = NSStringFromSelector(sel);
    
    //在字典中查找對(duì)應(yīng)的target
    id target = _methodsMap[methodName];

    //檢查target
    if (target && [target respondsToSelector:sel]) {
        return [target methodSignatureForSelector:sel];
    } else {
        return [super methodSignatureForSelector:sel];
    }
}

@end
-------------------------------------------------
#調(diào)用
- (void)viewDidLoad {
   [super viewDidLoad];
  ClassC *classC = [ClassC dealerProxy];
    [classC run];
    [classC walk];
}
運(yùn)行結(jié)果
2020-05-04 10:32:34.288075+0800 ProxyDemo[2006:407016] 跑步
2020-05-04 10:32:34.288303+0800 ProxyDemo[2006:407016] 走路

感覺(jué)NSProxy實(shí)現(xiàn)多繼承就像享扔,代理+組合+消息轉(zhuǎn)發(fā)的結(jié)合體底桂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伪很,隨后出現(xiàn)的幾起案子戚啥,更是在濱河造成了極大的恐慌,老刑警劉巖锉试,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猫十,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡呆盖,警方通過(guò)查閱死者的電腦和手機(jī)拖云,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)应又,“玉大人宙项,你說(shuō)我怎么就攤上這事≈昕福” “怎么了尤筐?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵汇荐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我盆繁,道長(zhǎng)掀淘,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任油昂,我火速辦了婚禮革娄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冕碟。我一直安慰自己拦惋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布安寺。 她就那樣靜靜地躺著厕妖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挑庶。 梳的紋絲不亂的頭發(fā)上叹放,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音挠羔,去河邊找鬼。 笑死埋嵌,一個(gè)胖子當(dāng)著我的面吹牛破加,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雹嗦,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼范舀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了了罪?” 一聲冷哼從身側(cè)響起锭环,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泊藕,沒(méi)想到半個(gè)月后辅辩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娃圆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年玫锋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讼呢。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撩鹿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悦屏,到底是詐尸還是另有隱情节沦,我是刑警寧澤键思,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站甫贯,受9級(jí)特大地震影響吼鳞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜获搏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一赖条、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧常熙,春花似錦纬乍、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至墓贿,卻和暖如春茧泪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背聋袋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工队伟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人幽勒。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓嗜侮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親啥容。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锈颗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348